1// Workflow Calculator - Vanilla JS Implementation 2const initWorkflowCalculator = () => { 3 const calculatorRoot = document.querySelector(".workflow-calculator"); 4 if (!calculatorRoot) return; 5 6 // Respect reduced motion preferences 7 const prefersReduced = window.matchMedia( 8 "(prefers-reduced-motion: reduce)", 9 ).matches; 10 11 // Get all calculation input fields 12 const calculationFields = calculatorRoot.querySelectorAll( 13 ".calculation-field--dtcs", 14 ); 15 const dcEmrField = calculatorRoot.querySelector("#dc-emr"); 16 const calculateButton = calculatorRoot.querySelector("#calculator-button"); 17 const resetButton = calculatorRoot.querySelector("#calculator-button-reset"); 18 const calculationForm = calculatorRoot.querySelector( 19 "#calculation-form-block", 20 ); 21 22 // Get result display elements 23 const minutesSavedDisplay = calculatorRoot.querySelector( 24 "#minutes-saved-dtcs", 25 ); 26 const hoursSavedDisplay = calculatorRoot.querySelector("#hours-saved-dtcs"); 27 const weeksSavedDisplay = calculatorRoot.querySelector("#weeks-saved-dtcs"); 28 const costSavedDisplay = calculatorRoot.querySelector("#cost-savings-dtcs"); 29 30 // Get hidden form fields 31 const minutesSavedHidden = calculatorRoot.querySelector("#minutes-saved"); 32 const hoursSavedHidden = calculatorRoot.querySelector("#hours-saved"); 33 const weeksSavedHidden = calculatorRoot.querySelector("#weeks-saved"); 34 const costSavedHidden = calculatorRoot.querySelector("#cost-saved"); 35 36 // Default values for initial load (match original calculator) 37 const defaultValues = { 38 recordRequests: "300", 39 recordProcess: "30", 40 staffWork: "40", 41 dcEmr: "$25", 42 }; 43 44 // Validation state variables 45 const validationState = { 46 recordRequests: true, 47 recordProcess: true, 48 staffWork: true, 49 dcEmr: true, 50 }; 51 52 // Initialize default values 53 const initializeDefaults = () => { 54 const recordRequestsField = 55 calculatorRoot.querySelector("#record-requests"); 56 const recordProcessField = calculatorRoot.querySelector("#record-process"); 57 const staffWorkField = calculatorRoot.querySelector("#staff-work"); 58 const dcEmrField = calculatorRoot.querySelector("#dc-emr"); 59 60 if (recordRequestsField) 61 recordRequestsField.value = defaultValues.recordRequests; 62 if (recordProcessField) 63 recordProcessField.value = defaultValues.recordProcess; 64 if (staffWorkField) staffWorkField.value = defaultValues.staffWork; 65 if (dcEmrField) dcEmrField.value = defaultValues.dcEmr; 66 }; 67 68 // Format number with commas 69 const formatNumberWithCommas = (input) => { 70 let value = input.value; 71 value = value.replace(/[^0-9.]/g, ""); 72 73 const parts = value.split("."); 74 const integerPart = parts[0]; 75 const decimalPart = parts.length > 1 ? "." + parts[1] : ""; 76 77 const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ","); 78 const formattedValue = formattedInteger + decimalPart; 79 80 input.value = formattedValue; 81 }; 82 83 // Format currency input for hourly rate ($) 84 const formatCurrencyInput = (input) => { 85 let value = input.value; 86 value = value.replace(/[^0-9.]/g, ""); 87 88 if (value === "") { 89 input.value = ""; 90 return; 91 } 92 93 const parts = value.split("."); 94 const integerPart = parts[0]; 95 const decimalPart = parts.length > 1 ? "." + parts[1].slice(0, 2) : ""; 96 97 const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ","); 98 99 input.value = "$" + formattedInteger + decimalPart; 100 }; 101 102 // Error handling functions 103 const showError = (fieldId, errorText) => { 104 const field = calculatorRoot.querySelector(fieldId); 105 const errorElement = field.nextElementSibling; 106 107 field.classList.add("input-error-dtcs"); 108 if (errorElement && errorElement.classList.contains("text-error-dtcs")) { 109 errorElement.textContent = errorText; 110 errorElement.style.display = "block"; 111 } 112 }; 113 114 const hideError = (fieldId) => { 115 const field = calculatorRoot.querySelector(fieldId); 116 const errorElement = field.nextElementSibling; 117 118 field.classList.remove("input-error-dtcs"); 119 if (errorElement && errorElement.classList.contains("text-error-dtcs")) { 120 errorElement.textContent = ""; 121 errorElement.style.display = "none"; 122 } 123 }; 124 125 // Validation functions 126 const validateField = (fieldId, fieldName) => { 127 const field = calculatorRoot.querySelector(fieldId); 128 const value = field.value.replace(/,/g, ""); 129 130 if (value === "") { 131 showError(fieldId, "Please enter a valid number."); 132 validationState[fieldName] = false;
133 return false; 134 } 135 136 if (parseFloat(value) < 1) { 137 showError(fieldId, "Please enter a value more than or equal to 1."); 138 validationState[fieldName] = false; 139 return false; 140 } 141 142 hideError(fieldId); 143 validationState[fieldName] = true; 144 return true; 145 }; 146 147 // Validate hourly rate (currency) 148 const validateDcEmr = () => { 149 const field = calculatorRoot.querySelector("#dc-emr"); 150 const value = field.value.replace(/[$,]/g, ""); 151 152 if (value === "") { 153 showError("#dc-emr", "Please enter a valid hourly rate."); 154 validationState.dcEmr = false; 155 return false; 156 } 157 158 if (parseFloat(value) < 1) { 159 showError("#dc-emr", "Please enter a value more than or equal to 1."); 160 validationState.dcEmr = false; 161 return false; 162 } 163 164 hideError("#dc-emr"); 165 validationState.dcEmr = true; 166 return true; 167 }; 168 169 // Reset button text and state 170 const resetButtonState = () => { 171 if (calculateButton) { 172 calculateButton.textContent = 173 calculateButton.querySelector("span")?.textContent || "Calculate"; 174 calculateButton.classList.remove("processing", "successful"); 175 } 176 }; 177 178 // Calculate results (including Cost Savings) 179 const performCalculation = () => { 180 const recordRequestsField = 181 calculatorRoot.querySelector("#record-requests"); 182 const recordProcessField = calculatorRoot.querySelector("#record-process"); 183 const staffWorkField = calculatorRoot.querySelector("#staff-work"); 184 const dcEmrField = calculatorRoot.querySelector("#dc-emr"); 185 186 if ( 187 !recordRequestsField || 188 !recordProcessField || 189 !staffWorkField || 190 !dcEmrField 191 ) 192 return; 193 194 // Get values and parse them 195 const recordRequests = 196 parseFloat(recordRequestsField.value.replace(/,/g, "")) || 0; 197 const recordProcess = 198 parseFloat(recordProcessField.value.replace(/,/g, "")) || 0; 199 const staffWork = parseFloat(staffWorkField.value.replace(/,/g, "")) || 0; 200 const hourlyRate = 201 parseFloat(dcEmrField.value.replace(/[$,]/g, "")) || 0; 202 203 // Perform calculations (matching original logic: rr * rp, etc.) 204 const minutesSaved = Math.floor(recordRequests * recordProcess); 205 const hoursSaved = Math.floor(minutesSaved / 60); 206 const weeksSaved = staffWork > 0 ? hoursSaved / staffWork : 0; 207 const costSaved = Math.floor(hoursSaved * hourlyRate); 208 209 // Format results 210 const minutesSavedFormatted = minutesSaved.toLocaleString("en-US"); 211 const hoursSavedFormatted = hoursSaved.toLocaleString("en-US"); 212 const weeksSavedFormatted = weeksSaved.toLocaleString("en-US", { 213 minimumFractionDigits: 2, 214 maximumFractionDigits: 2, 215 }); 216 const costSavedFormatted = costSaved.toLocaleString("en-US"); 217 218 return { 219 minutesSaved: minutesSavedFormatted, 220 hoursSaved: hoursSavedFormatted, 221 weeksSaved: weeksSavedFormatted, 222 costSaved: costSavedFormatted, 223 }; 224 }; 225 226 // Display calculation results 227 const displayResults = (results) => { 228 if (minutesSavedDisplay) 229 minutesSavedDisplay.textContent = results.minutesSaved; 230 if (hoursSavedDisplay) hoursSavedDisplay.textContent = results.hoursSaved; 231 if (weeksSavedDisplay) weeksSavedDisplay.textContent = results.weeksSaved; 232 if (costSavedDisplay) 233 costSavedDisplay.textContent = "$" + results.costSaved; 234 235 // Update hidden fields 236 if (minutesSavedHidden) minutesSavedHidden.value = results.minutesSaved; 237 if (hoursSavedHidden) hoursSavedHidden.value = results.hoursSaved; 238 if (weeksSavedHidden) weeksSavedHidden.value = results.weeksSaved; 239 if (costSavedHidden) costSavedHidden.value = results.costSaved; 240 }; 241 242 // Scroll to results section 243 const scrollToResults = () => { 244 const resultsSection = calculatorRoot.querySelector("#calculation-results"); 245 if (resultsSection) { 246 const offsetTop = 247 resultsSection.getBoundingClientRect().top + 248 window.pageYOffset - 249 100; 250 console.log("Scrolling to:", offsetTop); 251 window.scrollTo({ 252 top: offsetTop - 50, 253 behavior: "smooth", 254 }); 255 } 256 }; 257 258 // Reset form to defaults 259 const resetForm = () => { 260 // Reset field values 261 const recordRequestsField = 262 calculatorRoot.querySelector("#record-requests"); 263 const recordProcessField = calculatorRoot.querySelector("#record-process"); 264 const staffWorkField = calculatorRoot.querySelector("#staff-work"); 265 const dcEmrField = calculatorRoot.querySelector("#dc-emr"); 266 267 if (recordRequestsField) 268 recordRequestsField.value = defaultValues.recordRequests; 269 if (recordProcessField) 270 recordProcessField.value = defaultValues.recordProcess; 271 if (staffWorkField) staffWorkField.value = defaultValues.staffWork; 272 if (dcEmrField) dcEmrField.value = defaultValues.dcEmr; 273 274 // Reset results display (match original screenshot) 275 if (minutesSavedDisplay) minutesSavedDisplay.textContent = "102,600"; 276 if (hoursSavedDisplay) hoursSavedDisplay.textContent = "1,710";
277 if (weeksSavedDisplay) weeksSavedDisplay.textContent = "42.75"; 278 if (costSavedDisplay) costSavedDisplay.textContent = "$42,750"; 279 280 // Reset hidden fields 281 if (minutesSavedHidden) minutesSavedHidden.value = ""; 282 if (hoursSavedHidden) hoursSavedHidden.value = ""; 283 if (weeksSavedHidden) weeksSavedHidden.value = ""; 284 if (costSavedHidden) costSavedHidden.value = ""; 285 286 // Hide reset button 287 if (resetButton) { 288 resetButton.style.display = "none"; 289 } 290 291 // Clear any error states 292 hideError("#record-requests"); 293 hideError("#record-process"); 294 hideError("#staff-work"); 295 hideError("#dc-emr"); 296 }; 297 298 // Event listeners 299 const setupEventListeners = () => { 300 // Format numeric inputs with commas 301 calculationFields.forEach((field) => { 302 field.addEventListener("input", () => formatNumberWithCommas(field)); 303 }); 304 305 // Format currency input for hourly rate 306 if (dcEmrField) { 307 dcEmrField.addEventListener("input", () => 308 formatCurrencyInput(dcEmrField), 309 ); 310 } 311 312 // Calculate button click handler 313 if (calculateButton) { 314 calculateButton.addEventListener("click", (e) => { 315 e.preventDefault(); 316 317 // Validate all fields 318 const isRecordRequestsValid = validateField( 319 "#record-requests", 320 "recordRequests", 321 ); 322 const isRecordProcessValid = validateField( 323 "#record-process", 324 "recordProcess", 325 ); 326 const isStaffWorkValid = validateField("#staff-work", "staffWork"); 327 const isDcEmrValid = validateDcEmr(); 328 329 // Button processing animation 330 calculateButton.textContent = "Calculating..."; 331 calculateButton.classList.add("processing"); 332 333 setTimeout(() => { 334 if ( 335 isRecordRequestsValid && 336 isRecordProcessValid && 337 isStaffWorkValid && 338 isDcEmrValid 339 ) { 340 // Perform calculation 341 const results = performCalculation(); 342 343 if (results) { 344 // Success animation 345 calculateButton.textContent = "Calculated"; 346 calculateButton.classList.remove("processing"); 347 calculateButton.classList.add("successful"); 348 349 // Display results after delay 350 setTimeout(() => { 351 displayResults(results); 352 if (resetButton) resetButton.style.display = "inline-block"; 353 }, 300); 354 355 // Scroll to results 356 setTimeout(() => { 357 scrollToResults(); 358 }, 400); 359 360 // Reset button state 361 setTimeout(() => { 362 resetButtonState(); 363 }, 1400); 364 } 365 } else { 366 // Reset button on validation failure 367 setTimeout(() => { 368 resetButtonState(); 369 }, 100); 370 } 371 }, 590); 372 }); 373 } 374 375 // Reset button click handler 376 if (resetButton) { 377 resetButton.addEventListener("click", (e) => { 378 e.preventDefault(); 379 resetForm(); 380 }); 381 } 382 383 // Enter key support 384 const formElements = calculatorRoot.querySelectorAll( 385 ".calculation-field--dtcs, #dc-emr", 386 ); 387 formElements.forEach((element) => { 388 element.addEventListener("keypress", (e) => { 389 if (e.which === 13 || e.keyCode === 13) { 390 e.preventDefault(); 391 if (calculateButton) calculateButton.click(); 392 } 393 }); 394 }); 395 396 if (calculationForm) { 397 calculationForm.addEventListener("keypress", (e) => { 398 if (e.which === 13 || e.keyCode === 13) { 399 e.preventDefault(); 400 if (calculateButton) calculateButton.click(); 401 } 402 }); 403 } 404 }; 405 406 // Initialize component 407 const init = () => { 408 initializeDefaults(); 409 setupEventListeners(); 410 }; 411 412 // Run initialization
413 init(); 414}; 415 416// Initialize on document ready 417jQuery(document).ready(() => { 418 initWorkflowCalculator(); 419});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.