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
- 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.
103 lines
3.2 KiB
JavaScript
103 lines
3.2 KiB
JavaScript
/**
|
||
* 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
|
||
}
|