Clarify keyword snapshot loading state

This commit is contained in:
DCCONSTRUCTIONS 2026-07-05 16:17:03 +03:00
parent 4d82de82c1
commit 3951a9cbc5
2 changed files with 85 additions and 11 deletions

View File

@ -4379,6 +4379,8 @@ function SeoAnchorReviewWorkspace({
actionKey, actionKey,
activeFilter, activeFilter,
anchorReview, anchorReview,
contextProfileLabel,
contextProfileStatus,
demandCollectionProfile, demandCollectionProfile,
draftChangeCount, draftChangeCount,
draftDecisionCount, draftDecisionCount,
@ -4400,6 +4402,8 @@ function SeoAnchorReviewWorkspace({
actionKey: string | null; actionKey: string | null;
activeFilter: AnchorReviewFilter; activeFilter: AnchorReviewFilter;
anchorReview: AnchorReviewContract; anchorReview: AnchorReviewContract;
contextProfileLabel: string;
contextProfileStatus: "current" | "loading" | "snapshot";
demandCollectionProfile: DemandCollectionProfile; demandCollectionProfile: DemandCollectionProfile;
draftChangeCount: number; draftChangeCount: number;
draftDecisionCount: number; draftDecisionCount: number;
@ -4441,6 +4445,10 @@ function SeoAnchorReviewWorkspace({
<div> <div>
<strong>Этап 1 · Фиксация семантического базиса</strong> <strong>Этап 1 · Фиксация семантического базиса</strong>
<small>Выбираем смысловые якоря и вручную решаем, что отправлять в очередь Wordstat.</small> <small>Выбираем смысловые якоря и вручную решаем, что отправлять в очередь Wordstat.</small>
<small className="keyword-stage-profile-line">
Текущий активный профиль: <strong>{contextProfileLabel}</strong>
{contextProfileStatus === "loading" ? " · загружается" : contextProfileStatus === "snapshot" ? " · снимок" : ""}
</small>
<small> <small>
{getAnchorReviewStateLabel(anchorReview.state)} · очередь Wordstat {anchorReview.readiness.wordstatQueueCount} · {getAnchorReviewStateLabel(anchorReview.state)} · очередь Wordstat {anchorReview.readiness.wordstatQueueCount} ·
нормализовано: {anchorReview.readiness.normalizedAnchorCount} · предложено:{" "} нормализовано: {anchorReview.readiness.normalizedAnchorCount} · предложено:{" "}
@ -14288,11 +14296,17 @@ export function App() {
const activeKeywordContextSnapshotDetail = activeKeywordContextSnapshotKey const activeKeywordContextSnapshotDetail = activeKeywordContextSnapshotKey
? (keywordContextSnapshotDetails[activeKeywordContextSnapshotKey] ?? null) ? (keywordContextSnapshotDetails[activeKeywordContextSnapshotKey] ?? null)
: null; : null;
const isActiveKeywordContextSnapshotReplay = activeKeywordContextSnapshotId !== "current";
const isActiveKeywordContextSnapshotPending = Boolean(
isActiveKeywordContextSnapshotReplay && !activeKeywordContextSnapshotDetail
);
const isActiveKeywordContextSnapshotLoading = Boolean( const isActiveKeywordContextSnapshotLoading = Boolean(
activeKeywordContextSnapshotKey && loadingKeywordContextSnapshotKey === activeKeywordContextSnapshotKey isActiveKeywordContextSnapshotPending ||
(activeKeywordContextSnapshotKey && loadingKeywordContextSnapshotKey === activeKeywordContextSnapshotKey)
); );
const activeSemanticAnalysis = const activeSemanticAnalysis =
activeKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ?? (activeProject ? (semanticAnalyses[activeProject.id] ?? null) : null); activeKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ??
(isActiveKeywordContextSnapshotPending ? null : activeProject ? (semanticAnalyses[activeProject.id] ?? null) : null);
const activeContextReviewRaw = activeProject ? (contextReviews[activeProject.id] ?? null) : null; const activeContextReviewRaw = activeProject ? (contextReviews[activeProject.id] ?? null) : null;
const activeContextReview = const activeContextReview =
activeSemanticAnalysis && activeContextReviewRaw?.semanticRunId === activeSemanticAnalysis.runId activeSemanticAnalysis && activeContextReviewRaw?.semanticRunId === activeSemanticAnalysis.runId
@ -14300,10 +14314,11 @@ export function App() {
: null; : null;
const activeMarketEnrichment = const activeMarketEnrichment =
activeKeywordContextSnapshotDetail?.snapshot.marketEnrichment ?? activeKeywordContextSnapshotDetail?.snapshot.marketEnrichment ??
(activeProject ? (marketEnrichments[activeProject.id] ?? null) : null); (isActiveKeywordContextSnapshotPending ? null : activeProject ? (marketEnrichments[activeProject.id] ?? null) : null);
const activeKeywordAnalysisWorkflow = activeProject ? (keywordAnalysisWorkflows[activeProject.id] ?? null) : null; const activeKeywordAnalysisWorkflow = activeProject ? (keywordAnalysisWorkflows[activeProject.id] ?? null) : null;
const activeKeywordMap = const activeKeywordMap =
activeKeywordContextSnapshotDetail?.snapshot.keywordMap ?? (activeProject ? (keywordMaps[activeProject.id] ?? null) : null); activeKeywordContextSnapshotDetail?.snapshot.keywordMap ??
(isActiveKeywordContextSnapshotPending ? null : activeProject ? (keywordMaps[activeProject.id] ?? null) : null);
const activeQuotaProjectId = activeProject?.id ?? null; const activeQuotaProjectId = activeProject?.id ?? null;
useEffect(() => { useEffect(() => {
@ -14884,7 +14899,11 @@ export function App() {
!isPersistingActiveKeywordMap !isPersistingActiveKeywordMap
); );
const isSubmittingActiveKeywordStrategy = isPersistingActiveKeywordMap && persistingKeywordMapMode === "strategy"; const isSubmittingActiveKeywordStrategy = isPersistingActiveKeywordMap && persistingKeywordMapMode === "strategy";
const isActiveKeywordContextSnapshotReplay = activeKeywordContextSnapshotId !== "current"; const activeKeywordContextSnapshotSummary =
activeKeywordContextSnapshotId !== "current"
? (activeKeywordContextSnapshotList.find((snapshot) => snapshot.id === activeKeywordContextSnapshotId) ?? null)
: null;
const activeKeywordContextProfileLabel = activeKeywordContextSnapshotSummary?.label ?? "Текущий контекст";
const activeKeywordContextSnapshotOptions: NdcGlassSelectOption<string>[] = [ const activeKeywordContextSnapshotOptions: NdcGlassSelectOption<string>[] = [
{ label: "Текущий контекст", value: "current" }, { label: "Текущий контекст", value: "current" },
...activeKeywordContextSnapshotList.map((snapshot) => ({ ...activeKeywordContextSnapshotList.map((snapshot) => ({
@ -14933,7 +14952,7 @@ export function App() {
value={activeKeywordContextSnapshotId} value={activeKeywordContextSnapshotId}
/> />
{isActiveKeywordContextSnapshotReplay ? ( {isActiveKeywordContextSnapshotReplay ? (
<span className="keyword-context-profile-status"> <span className="keyword-context-profile-status" title={activeKeywordContextProfileLabel}>
{isActiveKeywordContextSnapshotLoading ? "загружаю снимок" : "снимок"} {isActiveKeywordContextSnapshotLoading ? "загружаю снимок" : "снимок"}
</span> </span>
) : null} ) : null}
@ -16467,8 +16486,26 @@ export function App() {
const projectKeywordContextSnapshotDetail = projectKeywordContextSnapshotKey const projectKeywordContextSnapshotDetail = projectKeywordContextSnapshotKey
? (keywordContextSnapshotDetails[projectKeywordContextSnapshotKey] ?? null) ? (keywordContextSnapshotDetails[projectKeywordContextSnapshotKey] ?? null)
: null; : null;
const projectKeywordContextSnapshotSummary =
projectKeywordContextSnapshotId !== "current"
? ((keywordContextSnapshots[project.id] ?? []).find(
(snapshot) => snapshot.id === projectKeywordContextSnapshotId
) ?? null)
: null;
const projectKeywordContextSnapshotPending = Boolean(
projectKeywordContextSnapshotId !== "current" && !projectKeywordContextSnapshotDetail
);
const projectKeywordContextProfileLabel =
projectKeywordContextSnapshotSummary?.label ?? "Текущий контекст";
const projectKeywordContextProfileStatus: "current" | "loading" | "snapshot" =
projectKeywordContextSnapshotId === "current"
? "current"
: projectKeywordContextSnapshotPending
? "loading"
: "snapshot";
const semanticAnalysis = const semanticAnalysis =
projectKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ?? (semanticAnalyses[project.id] ?? null); projectKeywordContextSnapshotDetail?.snapshot.semanticAnalysis ??
(projectKeywordContextSnapshotPending ? null : semanticAnalyses[project.id] ?? null);
const contextReview = contextReviews[project.id] ?? null; const contextReview = contextReviews[project.id] ?? null;
const isContextReviewAligned = Boolean( const isContextReviewAligned = Boolean(
semanticAnalysis && contextReview && contextReview.semanticRunId === semanticAnalysis.runId semanticAnalysis && contextReview && contextReview.semanticRunId === semanticAnalysis.runId
@ -16476,10 +16513,14 @@ export function App() {
const activeContextReview = isContextReviewAligned ? contextReview : null; const activeContextReview = isContextReviewAligned ? contextReview : null;
const projectOntology = projectOntologies[project.id] ?? null; const projectOntology = projectOntologies[project.id] ?? null;
const marketEnrichment = const marketEnrichment =
projectKeywordContextSnapshotDetail?.snapshot.marketEnrichment ?? (marketEnrichments[project.id] ?? null); projectKeywordContextSnapshotDetail?.snapshot.marketEnrichment ??
(projectKeywordContextSnapshotPending ? null : marketEnrichments[project.id] ?? null);
const keywordCleaning = const keywordCleaning =
projectKeywordContextSnapshotDetail?.snapshot.keywordCleaning ?? (keywordCleanings[project.id] ?? null); projectKeywordContextSnapshotDetail?.snapshot.keywordCleaning ??
const keywordMap = projectKeywordContextSnapshotDetail?.snapshot.keywordMap ?? (keywordMaps[project.id] ?? null); (projectKeywordContextSnapshotPending ? null : keywordCleanings[project.id] ?? null);
const keywordMap =
projectKeywordContextSnapshotDetail?.snapshot.keywordMap ??
(projectKeywordContextSnapshotPending ? null : keywordMaps[project.id] ?? null);
const seoStrategy = seoStrategies[project.id] ?? null; const seoStrategy = seoStrategies[project.id] ?? null;
const yandexEvidence = yandexEvidences[project.id] ?? null; const yandexEvidence = yandexEvidences[project.id] ?? null;
const serpInterpretation = serpInterpretations[project.id] ?? null; const serpInterpretation = serpInterpretations[project.id] ?? null;
@ -16507,7 +16548,8 @@ export function App() {
const isKeywordAnalysisRebuilding = isKeywordAnalysisWorkflowActive(keywordAnalysisWorkflow); const isKeywordAnalysisRebuilding = isKeywordAnalysisWorkflowActive(keywordAnalysisWorkflow);
const anchorReviewDraft = anchorReviewDraftDecisions[project.id] ?? {}; const anchorReviewDraft = anchorReviewDraftDecisions[project.id] ?? {};
const rawAnchorReview = const rawAnchorReview =
projectKeywordContextSnapshotDetail?.snapshot.anchorReview ?? marketEnrichment?.anchorReview ?? null; projectKeywordContextSnapshotDetail?.snapshot.anchorReview ??
(projectKeywordContextSnapshotPending ? null : marketEnrichment?.anchorReview ?? null);
const anchorReviewDraftCount = getAnchorReviewDraftDecisionList(rawAnchorReview, anchorReviewDraft).length; const anchorReviewDraftCount = getAnchorReviewDraftDecisionList(rawAnchorReview, anchorReviewDraft).length;
const savedDemandCollectionProfile = rawAnchorReview?.demandCollectionProfile ?? "contextual"; const savedDemandCollectionProfile = rawAnchorReview?.demandCollectionProfile ?? "contextual";
const anchorReviewDemandProfile = const anchorReviewDemandProfile =
@ -19536,6 +19578,15 @@ export function App() {
Прошло {keywordBasisElapsedLabel}. Обычно до 8 минут; окно можно оставить открытым. Прошло {keywordBasisElapsedLabel}. Обычно до 8 минут; окно можно оставить открытым.
</small> </small>
</div> </div>
) : projectKeywordContextSnapshotPending ? (
<div className="empty-state semantic-empty keyword-context-snapshot-loading">
<Loader2 className="spin" size={20} />
<strong>Загружаю сохранённый профиль</strong>
<span>{projectKeywordContextProfileLabel}</span>
<small>
Поднимаем сохранённые anchors, Wordstat evidence, cleaning и карту ключей из snapshot.
</small>
</div>
) : ( ) : (
<> <>
{semanticAnalysis && !activeContextReview ? ( {semanticAnalysis && !activeContextReview ? (
@ -19566,6 +19617,8 @@ export function App() {
actionKey={anchorReviewActionKey} actionKey={anchorReviewActionKey}
activeFilter={activeAnchorReviewFilter} activeFilter={activeAnchorReviewFilter}
anchorReview={anchorReview} anchorReview={anchorReview}
contextProfileLabel={projectKeywordContextProfileLabel}
contextProfileStatus={projectKeywordContextProfileStatus}
draftDecisionCount={anchorReviewDraftCount} draftDecisionCount={anchorReviewDraftCount}
demandCollectionProfile={anchorReviewDemandProfile} demandCollectionProfile={anchorReviewDemandProfile}
draftChangeCount={anchorReviewDraftChangeCount} draftChangeCount={anchorReviewDraftChangeCount}

View File

@ -2985,6 +2985,18 @@ input {
color: var(--ink); color: var(--ink);
} }
.keyword-stage-heading .keyword-stage-profile-line {
color: rgba(10, 12, 14, 0.72);
}
.keyword-stage-heading .keyword-stage-profile-line strong {
display: inline;
color: var(--ink);
font-size: inherit;
font-weight: 900;
line-height: inherit;
}
.keyword-stage-actions { .keyword-stage-actions {
display: inline-flex; display: inline-flex;
flex: 0 0 auto; flex: 0 0 auto;
@ -3117,6 +3129,15 @@ input {
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.keyword-context-snapshot-loading {
min-height: 14rem;
}
.keyword-context-snapshot-loading span {
max-width: min(48rem, 100%);
overflow-wrap: anywhere;
}
.keyword-workflow-step-list { .keyword-workflow-step-list {
display: grid; display: grid;
gap: 7px; gap: 7px;