Kairo-Jinkendo/frontend/src/context/ProgramScopeContext.jsx
Lars e50c04c96f
All checks were successful
Deploy Development / deploy (push) Successful in 53s
Test Suite / pytest-backend (push) Successful in 1m47s
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s
AP1.12b: Modal-Bearbeitung für Profil, Projekte und Backlog im Plan-Modus.
Ersetzt Inline-Formulare durch Dialoge; Scope-Breadcrumb aktualisiert sich nach Profil-Speichern.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 11:26:48 +02:00

174 lines
4.2 KiB
JavaScript

import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react'
import { useSearchParams } from 'react-router-dom'
import { getInitiative } from '../api/initiatives.js'
import { getProject } from '../api/projects.js'
import { scopedPath } from '../utils/routes.js'
const ProgramScopeContext = createContext(null)
export function ProgramScopeProvider({ children }) {
const [searchParams, setSearchParams] = useSearchParams()
const initiativeId = searchParams.get('initiative') || ''
const projectId = searchParams.get('project') || ''
const [initiativeTitle, setInitiativeTitle] = useState('')
const [projectTitle, setProjectTitle] = useState('')
useEffect(() => {
if (!initiativeId) {
setInitiativeTitle('')
return
}
let cancelled = false
getInitiative(initiativeId)
.then((data) => {
if (!cancelled) setInitiativeTitle(data.title || '')
})
.catch(() => {
if (!cancelled) setInitiativeTitle('')
})
return () => {
cancelled = true
}
}, [initiativeId])
useEffect(() => {
if (!projectId) {
setProjectTitle('')
return
}
let cancelled = false
getProject(projectId)
.then((data) => {
if (!cancelled) setProjectTitle(data.title || '')
})
.catch(() => {
if (!cancelled) setProjectTitle('')
})
return () => {
cancelled = true
}
}, [projectId])
const setScope = useCallback(
(next) => {
const params = new URLSearchParams(searchParams)
if ('initiativeId' in next) {
if (next.initiativeId) params.set('initiative', next.initiativeId)
else params.delete('initiative')
}
if ('projectId' in next) {
if (next.projectId) params.set('project', next.projectId)
else params.delete('project')
}
if (next.clearProject) params.delete('project')
setSearchParams(params, { replace: true })
},
[searchParams, setSearchParams],
)
const clearScope = useCallback(() => {
setSearchParams({}, { replace: true })
}, [setSearchParams])
const refreshInitiativeMeta = useCallback(() => {
if (!initiativeId) return
getInitiative(initiativeId)
.then((data) => setInitiativeTitle(data.title || ''))
.catch(() => setInitiativeTitle(''))
}, [initiativeId])
const applyFromInitiative = useCallback(
(id, { keepProject = false } = {}) => {
if (!id) return
setScope({
initiativeId: id,
projectId: keepProject ? projectId : '',
clearProject: !keepProject,
})
},
[projectId, setScope],
)
const applyFromProject = useCallback(
(project) => {
if (!project?.initiative_id) return
setScope({
initiativeId: project.initiative_id,
projectId: project.id,
})
},
[setScope],
)
const applyFromAction = useCallback(
(action) => {
if (!action?.initiative_id) return
setScope({
initiativeId: action.initiative_id,
projectId: action.project_id || '',
clearProject: !action.project_id,
})
},
[setScope],
)
const hrefWithScope = useCallback(
(path) => scopedPath(path, { initiativeId, projectId }),
[initiativeId, projectId],
)
const value = useMemo(
() => ({
initiativeId,
projectId,
initiativeTitle,
projectTitle,
setScope,
clearScope,
applyFromInitiative,
applyFromProject,
applyFromAction,
hrefWithScope,
refreshInitiativeMeta,
hasScope: Boolean(initiativeId),
}),
[
initiativeId,
projectId,
initiativeTitle,
projectTitle,
setScope,
clearScope,
applyFromInitiative,
applyFromProject,
applyFromAction,
hrefWithScope,
refreshInitiativeMeta,
],
)
return (
<ProgramScopeContext.Provider value={value}>{children}</ProgramScopeContext.Provider>
)
}
export function useProgramScope() {
const ctx = useContext(ProgramScopeContext)
if (!ctx) {
throw new Error('useProgramScope must be used within ProgramScopeProvider')
}
return ctx
}
export function useOptionalProgramScope() {
return useContext(ProgramScopeContext)
}