Enhance TrainingCoachPage and trainingPlanUtils with split rejoin transition logic
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
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 1m11s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 34s
Test Suite / playwright-tests (pull_request) Successful in 1m20s
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
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 1m11s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 34s
Test Suite / playwright-tests (pull_request) Successful in 1m20s
- Introduced a new utility function to determine when to prompt for a split rejoin transition between phases, improving user guidance during training sessions. - Updated TrainingCoachPage to incorporate this logic, enhancing the flow of navigation through training timelines. - Refactored button actions to provide clearer options for users when managing group transitions, optimizing the user experience during training.
This commit is contained in:
parent
5e5350d5ac
commit
a4f11a8225
|
|
@ -13,6 +13,7 @@ import {
|
||||||
coachBranchPicksStorageKey,
|
coachBranchPicksStorageKey,
|
||||||
coachOutlineGroupsFromTimeline,
|
coachOutlineGroupsFromTimeline,
|
||||||
coachShouldPromptSplitRejoin,
|
coachShouldPromptSplitRejoin,
|
||||||
|
coachShouldPromptSplitRejoinTransition,
|
||||||
durationOverridesMapFromDeltas,
|
durationOverridesMapFromDeltas,
|
||||||
findCoachTimelineJumpIndexForPhase,
|
findCoachTimelineJumpIndexForPhase,
|
||||||
flattenPlanTimeline,
|
flattenPlanTimeline,
|
||||||
|
|
@ -549,6 +550,14 @@ export default function TrainingCoachPage() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const nextIdx = safeStep + 1
|
||||||
|
if (nextIdx < timeline.length) {
|
||||||
|
const rejoinMid = coachShouldPromptSplitRejoinTransition(unit, timeline[safeStep], timeline[nextIdx])
|
||||||
|
if (rejoinMid) {
|
||||||
|
setSplitRejoinPrompt(rejoinMid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
setStep((s) => clampStep(s + 1, timeline.length))
|
setStep((s) => clampStep(s + 1, timeline.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -620,7 +629,6 @@ export default function TrainingCoachPage() {
|
||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
await api.updateTrainingUnit(idNum, payload)
|
await api.updateTrainingUnit(idNum, payload)
|
||||||
await reloadUnit()
|
|
||||||
setTrainerAppend('')
|
setTrainerAppend('')
|
||||||
try {
|
try {
|
||||||
sessionStorage.removeItem(storageDeltasKey(idNum))
|
sessionStorage.removeItem(storageDeltasKey(idNum))
|
||||||
|
|
@ -630,14 +638,15 @@ export default function TrainingCoachPage() {
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
setSearchParams({}, { replace: true })
|
|
||||||
setStep(0)
|
|
||||||
setDeltas({})
|
setDeltas({})
|
||||||
setBranchPicks({})
|
setBranchPicks({})
|
||||||
setStreamChoiceHint(null)
|
setStreamChoiceHint(null)
|
||||||
|
setSplitRejoinPrompt(null)
|
||||||
setCoachDebriefPhase(false)
|
setCoachDebriefPhase(false)
|
||||||
setSaveOk('Gespeichert.')
|
|
||||||
setDebriefOpen(false)
|
setDebriefOpen(false)
|
||||||
|
setSaveOk('Gespeichert.')
|
||||||
|
setSearchParams({}, { replace: true })
|
||||||
|
navigate(`/planning/run/${unitId}`, { replace: true })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSaveOk(`Fehler: ${e.message || e}`)
|
setSaveOk(`Fehler: ${e.message || e}`)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -771,11 +780,11 @@ export default function TrainingCoachPage() {
|
||||||
? String(splitRejoinPrompt.phaseTitle).trim()
|
? String(splitRejoinPrompt.phaseTitle).trim()
|
||||||
: `Phase ${splitRejoinPrompt.phaseOrderIndex}`}
|
: `Phase ${splitRejoinPrompt.phaseOrderIndex}`}
|
||||||
{' — '}
|
{' — '}
|
||||||
alle Gruppen fertig?
|
alle Gruppen zusammen?
|
||||||
</p>
|
</p>
|
||||||
<p style={{ fontSize: '0.84rem', color: 'var(--text2)', margin: '0 0 12px', lineHeight: 1.45 }}>
|
<p style={{ fontSize: '0.84rem', color: 'var(--text2)', margin: '0 0 12px', lineHeight: 1.45 }}>
|
||||||
Diese Phase hat mehrere Streams. Kurz mit dem anderen Trainer klären, dann gemeinsam Ist-Zeiten und Speichern
|
Diese Phase hat mehrere Streams. Kurz mit dem anderen Trainer klären, dann gemeinsam weitermachen. Ist-Zeiten
|
||||||
(gilt auch, wenn danach kein weiterer Block mehr kommt).
|
und Speichern erfolgen in der Nachbereitung am Ende der Einheit.
|
||||||
</p>
|
</p>
|
||||||
<ul style={{ margin: '0 0 14px', paddingLeft: '1.2rem', fontSize: '0.82rem', color: 'var(--text2)' }}>
|
<ul style={{ margin: '0 0 14px', paddingLeft: '1.2rem', fontSize: '0.82rem', color: 'var(--text2)' }}>
|
||||||
{splitRejoinPrompt.streams.map((st) => (
|
{splitRejoinPrompt.streams.map((st) => (
|
||||||
|
|
@ -786,22 +795,36 @@ export default function TrainingCoachPage() {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
<button
|
{safeStep < timeline.length - 1 ? (
|
||||||
type="button"
|
<button
|
||||||
className="btn btn-primary"
|
type="button"
|
||||||
style={{ minHeight: '48px', fontWeight: 700 }}
|
className="btn btn-primary"
|
||||||
onClick={() => {
|
style={{ minHeight: '48px', fontWeight: 700 }}
|
||||||
setSplitRejoinPrompt(null)
|
onClick={() => {
|
||||||
setCoachDebriefPhase(true)
|
setSplitRejoinPrompt(null)
|
||||||
try {
|
setStep((s) => clampStep(s + 1, timeline.length))
|
||||||
sessionStorage.setItem(storageDebriefKey(idNum), '1')
|
}}
|
||||||
} catch {
|
>
|
||||||
/* ignore */
|
Gruppen zusammengeführt — weiter mit dem Plan
|
||||||
}
|
</button>
|
||||||
}}
|
) : (
|
||||||
>
|
<button
|
||||||
Alle Gruppen fertig — zur Nachbereitung
|
type="button"
|
||||||
</button>
|
className="btn btn-primary"
|
||||||
|
style={{ minHeight: '48px', fontWeight: 700 }}
|
||||||
|
onClick={() => {
|
||||||
|
setSplitRejoinPrompt(null)
|
||||||
|
setCoachDebriefPhase(true)
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(storageDebriefKey(idNum), '1')
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Alle Gruppen fertig — zur Nachbereitung
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button type="button" className="btn btn-secondary" style={{ minHeight: '44px' }} onClick={() => setSplitRejoinPrompt(null)}>
|
<button type="button" className="btn btn-secondary" style={{ minHeight: '44px' }} onClick={() => setSplitRejoinPrompt(null)}>
|
||||||
Zurück — andere Gruppe läuft noch
|
Zurück — andere Gruppe läuft noch
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -819,7 +842,9 @@ export default function TrainingCoachPage() {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ausnahme: trotzdem zur Nachbereitung
|
{safeStep < timeline.length - 1
|
||||||
|
? 'Ausnahme: jetzt schon zur Nachbereitung'
|
||||||
|
: 'Ausnahme: trotzdem zur Nachbereitung'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -519,6 +519,20 @@ export function coachShouldPromptSplitRejoin(unit, lastTimelineEntry) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nach dem letzten Block eines gewählten Streams: Rückfrage vor Ganzgruppenphase oder vor dem nächsten Split,
|
||||||
|
* wenn die aktuelle Parallelphase mehrere Streams hat.
|
||||||
|
*/
|
||||||
|
export function coachShouldPromptSplitRejoinTransition(unit, currentEntry, nextEntry) {
|
||||||
|
if (!currentEntry || !nextEntry) return null
|
||||||
|
const cRm = currentEntry.runMeta
|
||||||
|
if (!cRm || cRm.kind !== 'parallel' || cRm.streamOrder == null) return null
|
||||||
|
const intoWholeGroup = nextEntry.runMeta?.kind === 'whole_group'
|
||||||
|
const intoNextSplit = nextEntry.entryKind === COACH_ENTRY_BRANCH_GATE
|
||||||
|
if (!intoWholeGroup && !intoNextSplit) return null
|
||||||
|
return coachShouldPromptSplitRejoin(unit, currentEntry)
|
||||||
|
}
|
||||||
|
|
||||||
export function summarizeTimelineEntry(ent) {
|
export function summarizeTimelineEntry(ent) {
|
||||||
if (!ent) return ''
|
if (!ent) return ''
|
||||||
if (ent.entryKind === COACH_ENTRY_BRANCH_GATE) {
|
if (ent.entryKind === COACH_ENTRY_BRANCH_GATE) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user