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 ( {children} ) } 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) }