shinkan-jinkendo/frontend/src/utils/trainingDurationUtils.js
Lars 9353909fda
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m29s
Enhance Training Framework Programs with Session Duration and Filtering Features
- Added SQL aggregations for session duration (min/max) and goal titles in the training framework programs query.
- Updated the TrainingPlanningFrameworkImportModal component to include filtering options for focus areas, training types, and target groups.
- Implemented session duration display in the TrainingFrameworkProgramsListPage, improving user visibility of program details.
- Introduced utility functions for formatting session duration ranges, enhancing the overall user experience in training planning.
2026-05-20 13:19:40 +02:00

103 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Eingabe/Ausgabe geplanter Trainingsdauer (Minuten intern, Anzeige z. B. 1,5 h).
*/
export function parseDurationInput(raw) {
if (raw === '' || raw == null) return null
const s = String(raw).trim().toLowerCase().replace(/\s+/g, '')
if (!s) return null
const hMatch = s.match(/^(\d+(?:[.,]\d+)?)\s*h(?:\s*(\d{1,2}))?$/)
if (hMatch) {
const h = parseFloat(hMatch[1].replace(',', '.'))
const extraMin = hMatch[2] ? parseInt(hMatch[2], 10) : 0
if (!Number.isFinite(h) || h < 0) return null
const total = Math.round(h * 60) + (Number.isFinite(extraMin) ? extraMin : 0)
return total > 0 ? total : null
}
if (/^\d+(?:[.,]\d+)?$/.test(s)) {
const n = parseFloat(s.replace(',', '.'))
if (!Number.isFinite(n) || n <= 0) return null
if (s.includes(',') || s.includes('.') || n < 8) {
return Math.round(n * 60)
}
return Math.round(n)
}
const minMatch = s.match(/^(\d+)\s*min?$/)
if (minMatch) {
const m = parseInt(minMatch[1], 10)
return Number.isFinite(m) && m > 0 ? m : null
}
return null
}
export function formatDurationDisplay(minutes, { empty = '—' } = {}) {
if (minutes == null || minutes === '') return empty
const m = Number(minutes)
if (!Number.isFinite(m) || m <= 0) return empty
if (m % 60 === 0) return `${m / 60} h`
if (m >= 60) {
const h = Math.floor(m / 60)
const rest = m % 60
return `${h} h ${rest} Min`
}
return `${m} Min`
}
/** Formularfeld: Minuten → Anzeige-String (z. B. 90 → "1,5") */
export function minutesToDurationFieldValue(minutes) {
if (minutes == null || minutes === '') return ''
const m = Number(minutes)
if (!Number.isFinite(m) || m <= 0) return ''
if (m % 60 === 0) return String(m / 60)
if (m >= 60) {
const h = m / 60
return Number.isInteger(h) ? String(h) : String(Math.round(h * 10) / 10).replace('.', ',')
}
return String(m)
}
export function sumExercisePlannedMinutes(sections) {
let sum = 0
for (const sec of sections || []) {
for (const it of sec.items || []) {
if (it.item_type !== 'exercise') continue
const n = parseDurationInput(it.planned_duration_min)
if (n != null) sum += n
}
}
return sum
}
export function sumSectionPlannedMinutes(sections) {
let sum = 0
for (const sec of sections || []) {
const n = parseDurationInput(sec.planned_duration_min)
if (n != null) sum += n
}
return sum
}
/**
* Anzeige für Session-Dauer (ein Slot oder Min/Max über mehrere Sessions).
* @param {number|null|undefined} minMinutes
* @param {number|null|undefined} maxMinutes
*/
export function formatSessionDurationRange(minMinutes, maxMinutes, { empty = '—' } = {}) {
const lo = minMinutes != null && minMinutes !== '' ? Number(minMinutes) : null
const hi = maxMinutes != null && maxMinutes !== '' ? Number(maxMinutes) : null
if (lo != null && Number.isFinite(lo) && lo > 0) {
if (hi != null && Number.isFinite(hi) && hi > 0 && hi !== lo) {
return `${formatDurationDisplay(lo, { empty: '' })} ${formatDurationDisplay(hi, { empty: '' })}`
}
return formatDurationDisplay(lo, { empty })
}
if (hi != null && Number.isFinite(hi) && hi > 0) {
return formatDurationDisplay(hi, { empty })
}
return empty
}