All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m18s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m14s
216 lines
6.4 KiB
JavaScript
216 lines
6.4 KiB
JavaScript
/**
|
|
* Einheitlicher Rücksprung-Kontext für PWA-Navigation (siehe NAV_RETURN_CONTEXT_SPEC.md).
|
|
*/
|
|
import {
|
|
buildPlanningHubReturnState,
|
|
PLANNING_HUB_PATH,
|
|
planningHubPathFromReturnState,
|
|
} from './planningUnitRoutes'
|
|
|
|
export { PLANNING_HUB_PATH }
|
|
|
|
export const NAV_RETURN_STATE_KEY = 'appReturn'
|
|
export const EXERCISES_LIST_PATH = '/exercises'
|
|
export const TRAINING_MODULES_LIST_PATH = '/planning/training-modules'
|
|
export const PLAN_TEMPLATES_LIST_PATH = '/planning/plan-templates'
|
|
export const FRAMEWORK_PROGRAMS_LIST_PATH = '/planning/framework-programs'
|
|
export const SETTINGS_PATH = '/settings'
|
|
export const DASHBOARD_PATH = '/'
|
|
|
|
export function buildNavReturnContext(opts) {
|
|
const path = String(opts?.path || '').trim()
|
|
const label = String(opts?.label || '').trim()
|
|
if (!path || !label) return null
|
|
const ctx = { v: 1, path, label }
|
|
if (opts?.kind) ctx.kind = opts.kind
|
|
if (opts?.payload && typeof opts.payload === 'object') {
|
|
ctx.payload = opts.payload
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
export function buildExercisesListReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: EXERCISES_LIST_PATH,
|
|
label: 'Zurück zur Übungsliste',
|
|
kind: 'exerciseList',
|
|
})
|
|
}
|
|
|
|
export function buildTrainingModulesListReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: TRAINING_MODULES_LIST_PATH,
|
|
label: 'Zurück zur Modul-Bibliothek',
|
|
kind: 'trainingModulesList',
|
|
})
|
|
}
|
|
|
|
export function buildPlanTemplatesListReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: PLAN_TEMPLATES_LIST_PATH,
|
|
label: 'Zurück zu Vorlagen',
|
|
kind: 'planTemplatesList',
|
|
})
|
|
}
|
|
|
|
export function buildFrameworkProgramsListReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: FRAMEWORK_PROGRAMS_LIST_PATH,
|
|
label: 'Zurück zu Rahmenprogrammen',
|
|
kind: 'frameworkProgramsList',
|
|
})
|
|
}
|
|
|
|
export function buildSettingsReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: SETTINGS_PATH,
|
|
label: 'Zurück zu Einstellungen',
|
|
kind: 'settings',
|
|
})
|
|
}
|
|
|
|
export function buildDashboardReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: DASHBOARD_PATH,
|
|
label: 'Zurück zur Übersicht',
|
|
kind: 'dashboard',
|
|
})
|
|
}
|
|
|
|
export const MEDIA_LIBRARY_PATH = '/media'
|
|
|
|
export function buildMediaLibraryReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: MEDIA_LIBRARY_PATH,
|
|
label: 'Zurück zur Medienbibliothek',
|
|
kind: 'mediaLibrary',
|
|
})
|
|
}
|
|
|
|
export function buildTrainingRunReturnContext(unitId) {
|
|
const id = String(unitId || '').trim()
|
|
if (!id) return null
|
|
return buildNavReturnContext({
|
|
path: `/planning/run/${id}`,
|
|
label: 'Zurück zum Trainingsablauf',
|
|
kind: 'trainingRun',
|
|
payload: { unitId: id },
|
|
})
|
|
}
|
|
|
|
export function buildPlanningHubFallbackReturnContext() {
|
|
return buildNavReturnContext({
|
|
path: PLANNING_HUB_PATH,
|
|
label: 'Zurück zur Planung',
|
|
kind: 'planningHub',
|
|
})
|
|
}
|
|
|
|
/** Router-Link state mit appReturn (optional zusätzlicher State). */
|
|
export function linkStateWithAppReturn(returnContext, extraState) {
|
|
const state = { ...(extraState || {}) }
|
|
if (returnContext) state[NAV_RETURN_STATE_KEY] = returnContext
|
|
return Object.keys(state).length > 0 ? state : undefined
|
|
}
|
|
|
|
/**
|
|
* Rücksprung zur aktuellen Route (z. B. Einheiten-Editor).
|
|
*/
|
|
export function buildCurrentLocationReturnContext(location, label) {
|
|
const path = `${location?.pathname || ''}${location?.search || ''}`.trim()
|
|
if (!path) return null
|
|
return buildNavReturnContext({
|
|
path,
|
|
label: String(label || 'Zurück').trim(),
|
|
kind: 'currentLocation',
|
|
})
|
|
}
|
|
|
|
/** @param {ReturnType<typeof buildPlanningHubReturnState>|object} hubState */
|
|
export function buildPlanningHubReturnContext(hubState = {}) {
|
|
const payload = buildPlanningHubReturnState(hubState)
|
|
return buildNavReturnContext({
|
|
path: planningHubPathFromReturnState(payload),
|
|
label: 'Zurück zur Planung',
|
|
kind: 'planningHub',
|
|
payload,
|
|
})
|
|
}
|
|
|
|
/** Legacy planningReturn → appReturn */
|
|
export function appReturnFromPlanningReturn(planningReturn) {
|
|
if (!planningReturn || planningReturn.v !== 1) return null
|
|
return buildPlanningHubReturnContext(planningReturn)
|
|
}
|
|
|
|
/**
|
|
* @param {import('react-router-dom').Location|{ state?: object }|null|undefined} location
|
|
*/
|
|
export function readNavReturnFromLocation(location) {
|
|
const state = location?.state
|
|
if (!state || typeof state !== 'object') return null
|
|
const raw = state[NAV_RETURN_STATE_KEY]
|
|
if (raw?.v === 1 && raw.path && raw.label) return raw
|
|
if (state.planningReturn) return appReturnFromPlanningReturn(state.planningReturn)
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* @param {import('react-router-dom').Location|{ state?: object }|null|undefined} location
|
|
* @param {{ path: string, label: string }|null|undefined} fallback
|
|
*/
|
|
export function resolveNavReturnTarget(location, fallback) {
|
|
const ctx = readNavReturnFromLocation(location)
|
|
if (ctx?.path && ctx?.label) {
|
|
return { path: ctx.path, label: ctx.label, fromContext: true }
|
|
}
|
|
if (fallback?.path && fallback?.label) {
|
|
return { path: fallback.path, label: fallback.label, fromContext: false }
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* @param {import('react-router-dom').NavigateFunction} navigate
|
|
* @param {import('react-router-dom').Location|{ state?: object }|null|undefined} location
|
|
* @param {{ path?: string, label?: string }|null|undefined} fallback
|
|
*/
|
|
export function goNavReturn(navigate, location, fallback) {
|
|
const target = resolveNavReturnTarget(location, fallback)
|
|
if (target?.fromContext && target.path) {
|
|
navigate(target.path)
|
|
return
|
|
}
|
|
if (typeof window !== 'undefined' && window.history.length > 1) {
|
|
navigate(-1)
|
|
return
|
|
}
|
|
if (fallback?.path) {
|
|
navigate(fallback.path)
|
|
return
|
|
}
|
|
navigate('/')
|
|
}
|
|
|
|
/**
|
|
* @param {import('react-router-dom').NavigateFunction} navigate
|
|
* @param {string} to
|
|
* @param {ReturnType<typeof buildNavReturnContext>|null|undefined} returnContext
|
|
* @param {object} [options]
|
|
*/
|
|
export function navigateWithAppReturn(navigate, to, returnContext, options = {}) {
|
|
const state = { ...(options.state || {}) }
|
|
if (returnContext) state[NAV_RETURN_STATE_KEY] = returnContext
|
|
navigate(to, { ...options, state })
|
|
}
|
|
|
|
/**
|
|
* Bestehenden appReturn (oder Legacy planningReturn) beim Weiterleiten erhalten.
|
|
*/
|
|
export function preserveAppReturnOnNavigate(navigate, location, to, options = {}) {
|
|
const existing = readNavReturnFromLocation(location)
|
|
const state = { ...(options.state || {}) }
|
|
if (existing) state[NAV_RETURN_STATE_KEY] = existing
|
|
navigate(to, { ...options, state })
|
|
}
|