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
Ersetzt Inline-Formulare durch Dialoge; Scope-Breadcrumb aktualisiert sich nach Profil-Speichern. Co-authored-by: Cursor <cursoragent@cursor.com>
174 lines
4.2 KiB
JavaScript
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)
|
|
}
|