import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { useDeltaWatermarks } from "@govoplan/core-webui"; import type { ApiSettings } from "../../../types"; import { getCampaignWorkspaceDelta, type CampaignWorkspaceDeltaResponse } from "../../../api/campaigns"; import type { CampaignWorkspaceData } from "../utils/campaignView"; const initialData: CampaignWorkspaceData = { campaign: null, versions: [], currentVersion: null, summary: null }; type UseCampaignWorkspaceDataOptions = { includeCurrentVersion?: boolean; includeSummary?: boolean; includeVersions?: boolean; }; export function useCampaignWorkspaceData( settings: ApiSettings, campaignId: string, options: UseCampaignWorkspaceDataOptions = {} ) { const { includeCurrentVersion = true, includeSummary = false, includeVersions = false } = options; const [searchParams] = useSearchParams(); const selectedVersionId = searchParams.get("version"); const [data, setData] = useState(initialData); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const dataRef = useRef(initialData); const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks(); const queryKey = useMemo( () => JSON.stringify({ campaignId, selectedVersionId, includeCurrentVersion, includeSummary, includeVersions, apiBaseUrl: settings.apiBaseUrl, apiKey: settings.apiKey, accessToken: settings.accessToken }), [campaignId, selectedVersionId, includeCurrentVersion, includeSummary, includeVersions, settings.apiBaseUrl, settings.apiKey, settings.accessToken] ); const reload = useCallback(async () => { if (!campaignId) return; setLoading(true); setError(""); try { const shouldLoadVersions = includeCurrentVersion || includeVersions; let nextWatermark = getDeltaWatermark(queryKey); let merged: CampaignWorkspaceData = dataRef.current; let hasMore = false; do { const response = await getCampaignWorkspaceDelta(settings, campaignId, { versionId: selectedVersionId, includeCurrentVersion, includeSummary, includeVersions: shouldLoadVersions, since: nextWatermark, }); merged = mergeWorkspaceDelta(merged, response); nextWatermark = response.watermark ?? null; hasMore = response.has_more; } while (hasMore); setDeltaWatermark(queryKey, nextWatermark); dataRef.current = merged; setData(merged); } catch (err) { dataRef.current = initialData; setData(initialData); resetDeltaWatermark(queryKey); setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } }, [settings, campaignId, includeCurrentVersion, includeSummary, includeVersions, selectedVersionId, queryKey, getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark]); useEffect(() => { resetDeltaWatermark(queryKey); dataRef.current = initialData; setData(initialData); }, [queryKey, resetDeltaWatermark]); useEffect(() => { reload(); }, [reload]); return { data, loading, error, reload, setError }; } function mergeWorkspaceDelta(current: CampaignWorkspaceData, response: CampaignWorkspaceDeltaResponse): CampaignWorkspaceData { if (response.full) { return { campaign: response.campaign, versions: response.versions, currentVersion: response.current_version, summary: response.summary }; } const deletedVersionIds = new Set( response.deleted .filter((item) => item.resource_type === "campaign_version") .map((item) => item.id) ); const versionMap = new Map(current.versions.map((version) => [version.id, version])); for (const version of response.versions) { versionMap.set(version.id, version); } for (const versionId of deletedVersionIds) { versionMap.delete(versionId); } const currentVersionDeleted = current.currentVersion ? deletedVersionIds.has(current.currentVersion.id) : false; return { campaign: response.campaign ?? current.campaign, versions: Array.from(versionMap.values()).sort((left, right) => (right.version_number ?? 0) - (left.version_number ?? 0)), currentVersion: response.current_version ?? (currentVersionDeleted ? null : current.currentVersion), summary: response.summary ?? current.summary }; }