PageSourceSearch

https://www.datavant.com/wp-content/themes/datavant/zen-blocks/workflow-calculator/workflow-calculator.js

js datavant.com collected 2026-10-02 05:54:05 UTC 12,684 bytes, 419 lines download raw bytes

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.