2026-05-13 07:16:58 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Geführtes method_profile für Kombinationsübungen — Felder nach method_archetype.
|
|
|
|
|
|
* Unbekannte JSON-Schlüssel bleiben beim Zusammenführen erhalten (Erweiterbarkeit).
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const INT_MAX = 86400
|
|
|
|
|
|
|
2026-05-13 08:19:46 +02:00
|
|
|
|
/** Pro Station: zeitlich (Standard), mengenorientiert oder coachgeführt (ohne Arbeits-Countdown). */
|
|
|
|
|
|
export const SLOT_ADVANCE_MODES = Object.freeze(['timed', 'rep', 'manual'])
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizeAdvanceMode(v) {
|
|
|
|
|
|
const s = typeof v === 'string' ? v.trim().toLowerCase() : ''
|
|
|
|
|
|
if (s === 'rep' || s === 'reps' || s === 'count') return 'rep'
|
|
|
|
|
|
if (s === 'manual' || s === 'coach' || s === 'coach_led') return 'manual'
|
|
|
|
|
|
return 'timed'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 07:16:58 +02:00
|
|
|
|
function parseProfileJson(raw) {
|
|
|
|
|
|
if (typeof raw !== 'string' || !raw.trim()) return { ok: true, obj: {} }
|
|
|
|
|
|
try {
|
|
|
|
|
|
const p = JSON.parse(raw)
|
|
|
|
|
|
if (!p || typeof p !== 'object' || Array.isArray(p)) {
|
|
|
|
|
|
return { ok: false, error: 'Ablaufprofil muss ein JSON-Objekt sein.' }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { ok: true, obj: { ...p } }
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return { ok: false, error: 'Ablaufprofil (JSON): Syntax ungültig.' }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Pro Archetyp: UI-Feldbeschreibungen (Werte werden in method_profile geschrieben) */
|
|
|
|
|
|
export const METHOD_PROFILE_GUI_FIELDS = Object.freeze({
|
|
|
|
|
|
sequence_linear: [
|
2026-05-13 07:53:00 +02:00
|
|
|
|
{
|
|
|
|
|
|
key: 'rounds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Anzahl Gesamtdurchläufe (komplette Sequenz, alle Stationen nacheinander)',
|
|
|
|
|
|
min: 1,
|
|
|
|
|
|
max: 999,
|
|
|
|
|
|
},
|
2026-05-13 07:16:58 +02:00
|
|
|
|
{
|
|
|
|
|
|
key: 'hint_step_duration_sec',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Orientierung: Sekunden je Station/Schritt (optional)',
|
|
|
|
|
|
min: 5,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'block_intro_sec',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Einführung / Demon am Block Gesamt (Sek., optional)',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
circuit_rotate_time: [
|
2026-05-13 07:53:00 +02:00
|
|
|
|
{
|
|
|
|
|
|
key: 'rounds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label:
|
|
|
|
|
|
'Anzahl Gesamtdurchläufe (jede Station pro Sportler mehrfach beim Umlauf, z. B. 4 Stationen × 2 = zwei komplette Runden)',
|
|
|
|
|
|
min: 1,
|
|
|
|
|
|
max: 999,
|
|
|
|
|
|
},
|
2026-05-13 07:16:58 +02:00
|
|
|
|
{
|
|
|
|
|
|
key: 'work_seconds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Arbeitszeit pro Station (Sek.)',
|
|
|
|
|
|
min: 5,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'transition_seconds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Wechsel / Rotation (Sek., optional)',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'rest_seconds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Pause zwischen Runden oder Stationen-Folgen (Sek., optional)',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
2026-05-13 07:53:00 +02:00
|
|
|
|
],
|
|
|
|
|
|
circuit_all_parallel: [
|
2026-05-13 07:16:58 +02:00
|
|
|
|
{
|
|
|
|
|
|
key: 'rounds',
|
|
|
|
|
|
kind: 'int',
|
2026-05-13 07:53:00 +02:00
|
|
|
|
label: 'Anzahl Durchläufe (wenn alle parallel dieselbe Rundenlogik haben, optional)',
|
2026-05-13 07:16:58 +02:00
|
|
|
|
min: 1,
|
|
|
|
|
|
max: 999,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'explain_before_seconds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Zeitfenster Vorab‑Erklärung aller Stationen (Sek., optional)',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'simultaneous_start',
|
|
|
|
|
|
kind: 'bool',
|
|
|
|
|
|
label: 'Alle Stationen starten zusammen nach Erklärung',
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
station_parcour: [
|
2026-05-13 07:53:00 +02:00
|
|
|
|
{
|
|
|
|
|
|
key: 'rounds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Anzahl Durchläufe des Parcours (Start Station 1, alle Bahnpunkte, Wiederholung bei Bedarf)',
|
|
|
|
|
|
min: 1,
|
|
|
|
|
|
max: 999,
|
|
|
|
|
|
},
|
2026-05-13 07:16:58 +02:00
|
|
|
|
{
|
|
|
|
|
|
key: 'allow_free_visit_order',
|
|
|
|
|
|
kind: 'bool',
|
|
|
|
|
|
label: 'Reihenfolge der Besuche frei (Parcours / Abhaken-Logik später im Coach)',
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
pair_superset: [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'switch_seconds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Orientierung: Wechselpause A↔B (Sek., optional)',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'work_seconds_per_side',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Arbeit pro Rolle oder Seite (Sek., optional)',
|
|
|
|
|
|
min: 5,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
time_domain_interval: [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'work_seconds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Intervall: Belastungszeit (Sek.)',
|
|
|
|
|
|
min: 5,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'rest_seconds',
|
|
|
|
|
|
kind: 'int',
|
|
|
|
|
|
label: 'Intervall: Erholungszeit (Sek., optional)',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: INT_MAX,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'interval_rounds',
|
|
|
|
|
|
kind: 'int',
|
2026-05-13 07:53:00 +02:00
|
|
|
|
label: 'Anzahl Wiederholungen der Intervalldomäne (komplette Zyklen Arbeit/Pause)',
|
2026-05-13 07:16:58 +02:00
|
|
|
|
min: 1,
|
|
|
|
|
|
max: 999,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
free_method_block: [],
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Aktualisiert method_profile unter Beibehaltung nicht-GUI Schlüssel.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function updateProfileGuided(archetype, rawJson, key, parsedValue, kind) {
|
|
|
|
|
|
const arch = typeof archetype === 'string' ? archetype.trim() : ''
|
|
|
|
|
|
const parsed = parseProfileJson(rawJson)
|
|
|
|
|
|
if (!parsed.ok) return parsed
|
|
|
|
|
|
const next = { ...parsed.obj }
|
|
|
|
|
|
|
|
|
|
|
|
if (kind === 'bool') {
|
|
|
|
|
|
if (parsedValue) next[key] = true
|
|
|
|
|
|
else delete next[key]
|
|
|
|
|
|
} else if (kind === 'int') {
|
|
|
|
|
|
if (parsedValue === null || parsedValue === undefined || parsedValue === '') {
|
|
|
|
|
|
delete next[key]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const n = typeof parsedValue === 'number' ? parsedValue : parseInt(String(parsedValue), 10)
|
|
|
|
|
|
if (!Number.isFinite(n)) delete next[key]
|
|
|
|
|
|
else next[key] = n
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const outJson = JSON.stringify(next)
|
|
|
|
|
|
return { ok: true, obj: next, json: outJson === '{}' ? '{}' : outJson }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function setFullProfileRawJson(rawEditable) {
|
|
|
|
|
|
const parsed = parseProfileJson(rawEditable)
|
|
|
|
|
|
if (!parsed.ok) return parsed
|
|
|
|
|
|
const j = JSON.stringify(parsed.obj)
|
|
|
|
|
|
return { ok: true, obj: parsed.obj, json: j === '{}' ? '{}' : j }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 07:38:44 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Pfad für slot_profiles_v1 und ähnliche strukturierte Erweiterungen.
|
|
|
|
|
|
* Ungültiges JSON gibt { ok:false } zurück; mutator erhält geklontes Profil‑Objekt.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function patchMethodProfile(rawJson, mutator) {
|
|
|
|
|
|
const parsed = parseProfileJson(rawJson || '{}')
|
|
|
|
|
|
if (!parsed.ok) return parsed
|
|
|
|
|
|
const draft = { ...parsed.obj }
|
|
|
|
|
|
mutator(draft)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const j = JSON.stringify(draft)
|
|
|
|
|
|
return { ok: true, obj: draft, json: j === '{}' ? '{}' : j }
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return { ok: false, error: 'Ablaufprofil konnte nicht gespeichert werden.' }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Normalisiert slot_profiles_v1 aus dem gespeicherten Profil */
|
|
|
|
|
|
export function readSlotProfilesV1(profileObj) {
|
|
|
|
|
|
if (!profileObj || typeof profileObj !== 'object') return []
|
|
|
|
|
|
const raw = profileObj.slot_profiles_v1
|
|
|
|
|
|
if (!Array.isArray(raw)) return []
|
|
|
|
|
|
return raw.map((row) => {
|
|
|
|
|
|
if (!row || typeof row !== 'object') return null
|
|
|
|
|
|
const si = Number(row.slot_index)
|
2026-05-13 08:19:46 +02:00
|
|
|
|
const mode = normalizeAdvanceMode(row.advance_mode)
|
|
|
|
|
|
const out = {
|
2026-05-13 07:38:44 +02:00
|
|
|
|
slot_index: Number.isFinite(si) ? si : 0,
|
2026-05-13 08:19:46 +02:00
|
|
|
|
advance_mode: mode,
|
2026-05-13 07:38:44 +02:00
|
|
|
|
load_sec: normalizeOptionalNonNegInt(row.load_sec),
|
|
|
|
|
|
consecutive_reps: normalizeOptionalPositiveInt(row.consecutive_reps),
|
|
|
|
|
|
intra_rep_rest_sec: normalizeOptionalNonNegInt(row.intra_rep_rest_sec),
|
|
|
|
|
|
transition_after_sec: normalizeOptionalNonNegInt(row.transition_after_sec),
|
|
|
|
|
|
}
|
2026-05-13 08:19:46 +02:00
|
|
|
|
return out
|
2026-05-13 07:38:44 +02:00
|
|
|
|
}).filter(Boolean)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeOptionalNonNegInt(v) {
|
|
|
|
|
|
if (v === '' || v === undefined || v === null) return undefined
|
|
|
|
|
|
const n = typeof v === 'number' ? v : parseInt(String(v), 10)
|
|
|
|
|
|
if (!Number.isFinite(n) || n < 0) return undefined
|
|
|
|
|
|
return Math.round(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeOptionalPositiveInt(v) {
|
|
|
|
|
|
const n = normalizeOptionalNonNegInt(v)
|
|
|
|
|
|
if (n === undefined) return undefined
|
|
|
|
|
|
if (n < 1) return undefined
|
|
|
|
|
|
return n
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const SLOT_TIMING_FIELDS = /** @type {const} */ ([
|
|
|
|
|
|
'load_sec',
|
|
|
|
|
|
'consecutive_reps',
|
|
|
|
|
|
'intra_rep_rest_sec',
|
|
|
|
|
|
'transition_after_sec',
|
|
|
|
|
|
])
|
|
|
|
|
|
|
2026-05-13 08:19:46 +02:00
|
|
|
|
function slotProfileRowShouldKeep(nextRow) {
|
|
|
|
|
|
if (!nextRow || typeof nextRow !== 'object') return false
|
|
|
|
|
|
const mode = normalizeAdvanceMode(nextRow.advance_mode)
|
|
|
|
|
|
if (mode !== 'timed') return true
|
|
|
|
|
|
return SLOT_TIMING_FIELDS.some((k) => nextRow[k] !== undefined && nextRow[k] !== null)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function writeSlotProfilesV1Arr(profileDraft, arr) {
|
|
|
|
|
|
const sorted = [...arr].sort((a, b) => Number(a.slot_index) - Number(b.slot_index))
|
|
|
|
|
|
if (sorted.length === 0) delete profileDraft.slot_profiles_v1
|
|
|
|
|
|
else profileDraft.slot_profiles_v1 = sorted
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Steuert Ende der Arbeitsphase: Zeit, Wiederholungsziel oder nur manuell weiter. */
|
|
|
|
|
|
export function patchSlotAdvanceMode(profileDraft, slotIndex, modeRaw) {
|
|
|
|
|
|
const ix =
|
|
|
|
|
|
typeof slotIndex === 'number' && Number.isFinite(slotIndex)
|
|
|
|
|
|
? slotIndex
|
|
|
|
|
|
: parseInt(String(slotIndex), 10)
|
|
|
|
|
|
if (!Number.isFinite(ix)) return
|
|
|
|
|
|
|
|
|
|
|
|
const mode = normalizeAdvanceMode(modeRaw)
|
|
|
|
|
|
let arr = Array.isArray(profileDraft.slot_profiles_v1) ? [...profileDraft.slot_profiles_v1] : []
|
|
|
|
|
|
const found = arr.findIndex((r) => r && typeof r === 'object' && Number(r.slot_index) === ix)
|
|
|
|
|
|
|
|
|
|
|
|
const nextRow = {}
|
|
|
|
|
|
if (found >= 0 && arr[found] && typeof arr[found] === 'object') {
|
|
|
|
|
|
Object.assign(nextRow, arr[found])
|
|
|
|
|
|
}
|
|
|
|
|
|
nextRow.slot_index = ix
|
|
|
|
|
|
if (mode === 'timed') delete nextRow.advance_mode
|
|
|
|
|
|
else {
|
|
|
|
|
|
nextRow.advance_mode = mode
|
|
|
|
|
|
delete nextRow.load_sec
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let nextArr
|
|
|
|
|
|
if (!slotProfileRowShouldKeep(nextRow)) {
|
|
|
|
|
|
nextArr = found >= 0 ? arr.filter((_, i) => i !== found) : arr
|
|
|
|
|
|
} else if (found >= 0) {
|
|
|
|
|
|
nextArr = [...arr]
|
|
|
|
|
|
nextArr[found] = nextRow
|
|
|
|
|
|
} else {
|
|
|
|
|
|
nextArr = [...arr, nextRow]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
writeSlotProfilesV1Arr(profileDraft, nextArr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 07:38:44 +02:00
|
|
|
|
/** '', null = Feld entfernen; sonst gültige Zahl setzen */
|
|
|
|
|
|
export function patchSlotTimingField(profileDraft, slotIndex, field, rawInput) {
|
|
|
|
|
|
if (!SLOT_TIMING_FIELDS.includes(field)) return
|
|
|
|
|
|
const ix =
|
|
|
|
|
|
typeof slotIndex === 'number' && Number.isFinite(slotIndex)
|
|
|
|
|
|
? slotIndex
|
|
|
|
|
|
: parseInt(String(slotIndex), 10)
|
|
|
|
|
|
if (!Number.isFinite(ix)) return
|
|
|
|
|
|
|
|
|
|
|
|
let arr = Array.isArray(profileDraft.slot_profiles_v1) ? [...profileDraft.slot_profiles_v1] : []
|
|
|
|
|
|
let found = arr.findIndex((r) => r && typeof r === 'object' && Number(r.slot_index) === ix)
|
|
|
|
|
|
|
|
|
|
|
|
const nextRow = {}
|
|
|
|
|
|
if (found >= 0 && arr[found] && typeof arr[found] === 'object') {
|
|
|
|
|
|
Object.assign(nextRow, arr[found])
|
|
|
|
|
|
}
|
|
|
|
|
|
nextRow.slot_index = ix
|
|
|
|
|
|
|
|
|
|
|
|
if (rawInput === null || rawInput === undefined || String(rawInput).trim() === '') {
|
|
|
|
|
|
delete nextRow[field]
|
|
|
|
|
|
} else if (field === 'consecutive_reps') {
|
|
|
|
|
|
const n = normalizeOptionalPositiveInt(rawInput)
|
|
|
|
|
|
if (n === undefined) delete nextRow[field]
|
|
|
|
|
|
else nextRow[field] = n
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const n = normalizeOptionalNonNegInt(rawInput)
|
|
|
|
|
|
if (n === undefined) delete nextRow[field]
|
|
|
|
|
|
else nextRow[field] = n
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 08:19:46 +02:00
|
|
|
|
const keep = slotProfileRowShouldKeep(nextRow)
|
2026-05-13 07:38:44 +02:00
|
|
|
|
|
|
|
|
|
|
if (found >= 0) {
|
2026-05-13 08:19:46 +02:00
|
|
|
|
if (!keep) {
|
2026-05-13 07:38:44 +02:00
|
|
|
|
arr = arr.filter((_, i) => i !== found)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
arr[found] = nextRow
|
|
|
|
|
|
}
|
2026-05-13 08:19:46 +02:00
|
|
|
|
} else if (keep) {
|
2026-05-13 07:38:44 +02:00
|
|
|
|
arr.push(nextRow)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 08:19:46 +02:00
|
|
|
|
writeSlotProfilesV1Arr(profileDraft, arr)
|
2026-05-13 07:38:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Rotierender Zirkel: typische Ableitungen (setzt Sekunden konkret). */
|
|
|
|
|
|
export function applyCircuitRotateQuickRatio(profileDraft, preset) {
|
|
|
|
|
|
const wRaw = profileDraft.work_seconds
|
|
|
|
|
|
const work =
|
|
|
|
|
|
typeof wRaw === 'number' && Number.isFinite(wRaw) ? Math.round(wRaw) : parseInt(String(wRaw), 10)
|
|
|
|
|
|
if (!Number.isFinite(work) || work <= 0)
|
|
|
|
|
|
return { ok: false, error: 'Zuerst Arbeitszeit pro Station (Sek.) setzen.' }
|
|
|
|
|
|
profileDraft.timing_schema = profileDraft.timing_schema ?? 1
|
|
|
|
|
|
if (preset === 'transition_equals_work') {
|
|
|
|
|
|
profileDraft.transition_seconds = work
|
|
|
|
|
|
return { ok: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (preset === 'round_rest_equals_work') {
|
|
|
|
|
|
profileDraft.rest_seconds = work
|
|
|
|
|
|
return { ok: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (preset === 'round_rest_two_thirds_work') {
|
|
|
|
|
|
profileDraft.rest_seconds = Math.round((work * 2) / 3)
|
|
|
|
|
|
return { ok: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { ok: false, error: 'Unbekannte Schnellwahl.' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function applyIntervalDomainQuickRatio(profileDraft, preset) {
|
|
|
|
|
|
const wRaw = profileDraft.work_seconds
|
|
|
|
|
|
const work =
|
|
|
|
|
|
typeof wRaw === 'number' && Number.isFinite(wRaw) ? Math.round(wRaw) : parseInt(String(wRaw), 10)
|
|
|
|
|
|
if (!Number.isFinite(work) || work <= 0)
|
|
|
|
|
|
return { ok: false, error: 'Zuerst Belastungszeit Intervall (Sek.) setzen.' }
|
|
|
|
|
|
profileDraft.timing_schema = profileDraft.timing_schema ?? 1
|
|
|
|
|
|
if (preset === 'rest_equals_work') {
|
|
|
|
|
|
profileDraft.rest_seconds = work
|
|
|
|
|
|
return { ok: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (preset === 'rest_two_thirds_work') {
|
|
|
|
|
|
profileDraft.rest_seconds = Math.round((work * 2) / 3)
|
|
|
|
|
|
return { ok: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
return { ok: false, error: 'Unbekannte Schnellwahl.' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 07:16:58 +02:00
|
|
|
|
export { parseProfileJson, INT_MAX }
|