"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { TemplateConfig } from "@/components/admin/builder/templates"; import { Button } from "@/components/ui/button"; import type { BuilderScreen } from "@/lib/admin/builder/types"; import { extractVariantOverrides, formatOverridePath, listOverridePaths, mergeScreenWithOverrides, } from "@/lib/admin/builder/variants"; import type { ListOptionDefinition, NavigationConditionDefinition, ScreenDefinition, ScreenVariantDefinition, } from "@/lib/funnel/types"; interface ScreenVariantsConfigProps { screen: BuilderScreen; allScreens: BuilderScreen[]; onChange: (variants: ScreenVariantDefinition[]) => void; } type ListBuilderScreen = BuilderScreen & { template: "list" }; type VariantDefinition = ScreenVariantDefinition; type VariantCondition = NavigationConditionDefinition; function ensureCondition(variant: VariantDefinition, fallbackScreenId: string): VariantCondition { const [condition] = variant.conditions; if (!condition) { return { screenId: fallbackScreenId, operator: "includesAny", optionIds: [], }; } return condition; } function VariantOverridesEditor({ baseScreen, overrides, onChange, }: { baseScreen: BuilderScreen; overrides: VariantDefinition["overrides"]; onChange: (overrides: VariantDefinition["overrides"]) => void; }) { const baseWithoutVariants = useMemo(() => { const clone = mergeScreenWithOverrides(baseScreen, {}); const sanitized = { ...clone } as BuilderScreen; if ("variants" in sanitized) { delete (sanitized as Partial).variants; } return sanitized; }, [baseScreen]); const mergedScreen = useMemo( () => mergeScreenWithOverrides(baseWithoutVariants, overrides), [baseWithoutVariants, overrides] ); const handleUpdate = useCallback( (updates: Partial) => { const nextScreen = mergeScreenWithOverrides(mergedScreen, updates as Partial); const nextOverrides = extractVariantOverrides(baseWithoutVariants, nextScreen); onChange(nextOverrides); }, [baseWithoutVariants, mergedScreen, onChange] ); return (
); } export function ScreenVariantsConfig({ screen, allScreens, onChange }: ScreenVariantsConfigProps) { const variants = useMemo( () => ((screen.variants ?? []) as VariantDefinition[]), [screen.variants] ); const [expandedVariant, setExpandedVariant] = useState(() => (variants.length > 0 ? 0 : null)); useEffect(() => { if (variants.length === 0) { setExpandedVariant(null); return; } if (expandedVariant === null) { setExpandedVariant(0); return; } if (expandedVariant >= variants.length) { setExpandedVariant(variants.length - 1); } }, [expandedVariant, variants]); const listScreens = useMemo( () => allScreens.filter((candidate): candidate is ListBuilderScreen => candidate.template === "list"), [allScreens] ); const optionMap = useMemo(() => { return listScreens.reduce>((accumulator, listScreen) => { accumulator[listScreen.id] = listScreen.list.options; return accumulator; }, {}); }, [listScreens]); const handleVariantsUpdate = useCallback( (nextVariants: VariantDefinition[]) => { onChange(nextVariants); }, [onChange] ); const addVariant = useCallback(() => { const fallbackScreen = listScreens[0] ?? (screen.template === "list" ? (screen as ListBuilderScreen) : undefined); if (!fallbackScreen) { return; } const firstOptionId = fallbackScreen.list.options[0]?.id; const newVariant: VariantDefinition = { conditions: [ { screenId: fallbackScreen.id, operator: "includesAny", optionIds: firstOptionId ? [firstOptionId] : [], }, ], overrides: {}, }; handleVariantsUpdate([...variants, newVariant]); setExpandedVariant(variants.length); }, [handleVariantsUpdate, listScreens, screen, variants]); const removeVariant = useCallback( (index: number) => { handleVariantsUpdate(variants.filter((_, variantIndex) => variantIndex !== index)); }, [handleVariantsUpdate, variants] ); const updateVariant = useCallback( (index: number, patch: Partial) => { handleVariantsUpdate( variants.map((variant, variantIndex) => variantIndex === index ? { ...variant, ...patch, conditions: patch.conditions ?? variant.conditions, overrides: patch.overrides ?? variant.overrides, } : variant ) ); }, [handleVariantsUpdate, variants] ); const updateCondition = useCallback( (index: number, updates: Partial) => { updateVariant(index, { conditions: [ { ...ensureCondition(variants[index], screen.id), ...updates, }, ], }); }, [screen.id, updateVariant, variants] ); const toggleOption = useCallback( (index: number, optionId: string) => { const condition = ensureCondition(variants[index], screen.id); const optionIds = new Set(condition.optionIds ?? []); if (optionIds.has(optionId)) { optionIds.delete(optionId); } else { optionIds.add(optionId); } updateCondition(index, { optionIds: Array.from(optionIds) }); }, [screen.id, updateCondition, variants] ); const handleScreenChange = useCallback( (variantIndex: number, screenId: string) => { const listScreen = listScreens.find((candidate) => candidate.id === screenId); const defaultOption = listScreen?.list.options[0]?.id; updateCondition(variantIndex, { screenId, optionIds: defaultOption ? [defaultOption] : [], }); }, [listScreens, updateCondition] ); const handleOperatorChange = useCallback( (variantIndex: number, operator: VariantCondition["operator"]) => { updateCondition(variantIndex, { operator }); }, [updateCondition] ); const handleOverridesChange = useCallback( (index: number, overrides: VariantDefinition["overrides"]) => { updateVariant(index, { overrides }); }, [updateVariant] ); const renderVariantSummary = useCallback( (variant: VariantDefinition) => { const condition = ensureCondition(variant, screen.id); const optionSummaries = (condition.optionIds ?? []).map((optionId) => { const options = optionMap[condition.screenId] ?? []; const option = options.find((item) => item.id === optionId); return option?.label ?? optionId; }); const listScreenTitle = listScreens.find((candidate) => candidate.id === condition.screenId)?.title.text; const operatorLabel = (() => { switch (condition.operator) { case "includesAll": return "все из"; case "includesExactly": return "точное совпадение"; default: return "любой из"; } })(); const overrideHighlights = listOverridePaths(variant.overrides ?? {}); return (
Экран условий: {listScreenTitle ?? condition.screenId} {operatorLabel}
{optionSummaries.length > 0 ? (
{optionSummaries.map((label) => ( {label} ))}
) : (
Пока нет выбранных ответов
)}
{(overrideHighlights.length > 0 ? overrideHighlights : ["Без изменений"]).map((item) => ( {item === "Без изменений" ? item : formatOverridePath(item)} ))}
); }, [listScreens, optionMap, screen.id] ); return (

Настройте альтернативные варианты контента без изменения переходов.

{listScreens.length === 0 ? (
Добавьте экран со списком, чтобы настроить вариативность.
) : variants.length === 0 ? (
Пока нет дополнительных вариантов.
) : (
{variants.map((variant, index) => { const condition = ensureCondition(variant, screen.id); const isExpanded = expandedVariant === index; const availableOptions = optionMap[condition.screenId] ?? []; return (
Вариант {index + 1}
{renderVariantSummary(variant)}
{isExpanded && (
Ответы {availableOptions.length === 0 ? (
В выбранном экране пока нет вариантов ответа.
) : (
{availableOptions.map((option) => { const isChecked = condition.optionIds?.includes(option.id) ?? false; return ( ); })}
)}
Настройка контента handleOverridesChange(index, overrides)} />
)}
); })}
)}
); }