extract calculator shared modules

This commit is contained in:
Daniel 2026-05-08 02:58:58 +02:00
parent 4a14a71151
commit e4ea553bee
4 changed files with 77 additions and 84 deletions

View file

@ -508,7 +508,7 @@
<script defer src="/js/documents.js"></script>
<script defer src="/js/calc-math.js"></script>
<script defer src="/js/drugs-loader.js"></script>
<script defer src="/js/calculators.js"></script>
<script type="module" src="/js/calculators.js"></script>
<script type="module" src="/js/bedside/index.js"></script>
<script defer src="/js/learningHub.js"></script>
<script defer src="/js/admin.js?v=7.1.3"></script>

View file

@ -1,7 +1,12 @@
import { parseAgeMonths, formatParsedAge } from './calculators/age.js';
import { initImageLightbox } from './calculators/lightbox.js';
// ============================================================
// CALCULATORS.JS — Pediatric clinical calculators
// ============================================================
initImageLightbox();
(function() {
var _inited = false;
document.addEventListener('tabChanged', function(e) {
@ -10,49 +15,6 @@
initCalculators();
});
// ── Flexible age parser ─────────────────────────────────────────────
// Accepts: "3y", "3 years", "29m", "29 months", "2y5m", "2 years 5 months",
// "15d", "15 days", "3y 2m 10d", "36" (months), "3.5 years", etc.
// Returns age in months (may be fractional), or null if unparseable.
function parseAgeMonths(input) {
if (input == null) return null;
var s = String(input).toLowerCase().trim();
if (!s) return null;
// Plain number with no unit: if >= 24 assume years, else months. Matches
// common shorthand ("36" for 3y, "6" for 6 months).
if (/^[\d.]+$/.test(s)) {
var n = parseFloat(s);
if (isNaN(n)) return null;
return n >= 24 ? n : n; // keep as months; user can add unit if they meant years
}
var total = 0;
var matched = false;
// Use (?![a-z]) instead of \b so "2y5m" terminates "y" correctly.
// Order matters: longest-first alternations (years before yrs before y, etc.).
var my = s.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/);
if (my) { total += parseFloat(my[1]) * 12; matched = true; }
var mm = s.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/);
if (mm) { total += parseFloat(mm[1]); matched = true; }
var mw = s.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/);
if (mw) { total += parseFloat(mw[1]) * 7 / 30.4375; matched = true; }
var md = s.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/);
if (md) { total += parseFloat(md[1]) / 30.4375; matched = true; }
return matched ? total : null;
}
function formatParsedAge(months) {
if (months < 1) {
var d = Math.round(months * 30.4375);
return '= ' + d + ' day' + (d === 1 ? '' : 's') + ' (' + months.toFixed(2) + ' mo)';
}
if (months < 24) {
return '= ' + (Math.round(months * 10) / 10) + ' months';
}
var yr = Math.floor(months / 12);
var mo = Math.round(months - yr * 12);
if (mo === 12) { yr += 1; mo = 0; }
return '= ' + yr + ' yr' + (mo ? ' ' + mo + ' mo' : '') + ' (' + Math.round(months) + ' mo total)';
}
// Expose for debugging / other modules
window._parseAgeMonths = parseAgeMonths;
@ -2899,43 +2861,3 @@
resultDiv.innerHTML = html;
}
})();
// ============================================================
// IMAGE LIGHTBOX — any element with data-img-src opens fullscreen
// ============================================================
(function() {
document.addEventListener('click', function(e) {
var opener = e.target.closest && e.target.closest('[data-img-src]');
if (opener) {
e.preventDefault();
var lb = document.getElementById('img-lightbox');
if (!lb) return;
var src = opener.dataset.imgSrc;
var title = opener.dataset.imgTitle || '';
document.getElementById('img-lightbox-img').src = src;
document.getElementById('img-lightbox-img').alt = title;
document.getElementById('img-lightbox-title').textContent = title;
document.getElementById('img-lightbox-open').href = src;
lb.style.display = 'flex';
return;
}
// Close on close button, on backdrop click (not on image), and on Escape (below)
var closeBtn = e.target.closest && e.target.closest('#img-lightbox-close');
var lb2 = document.getElementById('img-lightbox');
if (!lb2 || lb2.style.display !== 'flex') return;
if (closeBtn || e.target === lb2) {
lb2.style.display = 'none';
document.getElementById('img-lightbox-img').src = '';
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
var lb = document.getElementById('img-lightbox');
if (lb && lb.style.display === 'flex') {
lb.style.display = 'none';
document.getElementById('img-lightbox-img').src = '';
}
}
});
})();

View file

@ -0,0 +1,36 @@
export function parseAgeMonths(input) {
if (input == null) return null;
var s = String(input).toLowerCase().trim();
if (!s) return null;
// Plain number with no unit: keep as months; user can add a unit if they meant years.
if (/^[\d.]+$/.test(s)) {
var n = parseFloat(s);
if (isNaN(n)) return null;
return n;
}
var total = 0;
var matched = false;
var my = s.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/);
if (my) { total += parseFloat(my[1]) * 12; matched = true; }
var mm = s.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/);
if (mm) { total += parseFloat(mm[1]); matched = true; }
var mw = s.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/);
if (mw) { total += parseFloat(mw[1]) * 7 / 30.4375; matched = true; }
var md = s.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/);
if (md) { total += parseFloat(md[1]) / 30.4375; matched = true; }
return matched ? total : null;
}
export function formatParsedAge(months) {
if (months < 1) {
var d = Math.round(months * 30.4375);
return '= ' + d + ' day' + (d === 1 ? '' : 's') + ' (' + months.toFixed(2) + ' mo)';
}
if (months < 24) {
return '= ' + (Math.round(months * 10) / 10) + ' months';
}
var yr = Math.floor(months / 12);
var mo = Math.round(months - yr * 12);
if (mo === 12) { yr += 1; mo = 0; }
return '= ' + yr + ' yr' + (mo ? ' ' + mo + ' mo' : '') + ' (' + Math.round(months) + ' mo total)';
}

View file

@ -0,0 +1,35 @@
export function initImageLightbox() {
document.addEventListener('click', function(e) {
var opener = e.target.closest && e.target.closest('[data-img-src]');
if (opener) {
e.preventDefault();
var lb = document.getElementById('img-lightbox');
if (!lb) return;
var src = opener.dataset.imgSrc;
var title = opener.dataset.imgTitle || '';
document.getElementById('img-lightbox-img').src = src;
document.getElementById('img-lightbox-img').alt = title;
document.getElementById('img-lightbox-title').textContent = title;
document.getElementById('img-lightbox-open').href = src;
lb.style.display = 'flex';
return;
}
var closeBtn = e.target.closest && e.target.closest('#img-lightbox-close');
var lb2 = document.getElementById('img-lightbox');
if (!lb2 || lb2.style.display !== 'flex') return;
if (closeBtn || e.target === lb2) {
lb2.style.display = 'none';
document.getElementById('img-lightbox-img').src = '';
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
var lb = document.getElementById('img-lightbox');
if (lb && lb.style.display === 'flex') {
lb.style.display = 'none';
document.getElementById('img-lightbox-img').src = '';
}
}
});
}