"use client"; import { useEffect, useMemo, useState } from "react"; import { TextInput } from "@/components/ui/TextInput/TextInput"; import { Button } from "@/components/ui/button"; import { TemplateConfig } from "@/components/admin/builder/templates"; import { ScreenVariantsConfig } from "@/components/admin/builder/ScreenVariantsConfig"; import { useBuilderDispatch, useBuilderSelectedScreen, useBuilderState } from "@/lib/admin/builder/context"; import type { BuilderScreen } from "@/lib/admin/builder/types"; import type { NavigationRuleDefinition, ScreenDefinition, ScreenVariantDefinition, } from "@/lib/funnel/types"; import { cn } from "@/lib/utils"; import { validateBuilderState } from "@/lib/admin/builder/validation"; import { Section } from "./Section"; import { ValidationSummary } from "./ValidationSummary"; import { isListScreen, type ValidationIssues } from "./types"; export function BuilderSidebar() { const state = useBuilderState(); const dispatch = useBuilderDispatch(); const selectedScreen = useBuilderSelectedScreen(); const [activeTab, setActiveTab] = useState<"funnel" | "screen">(selectedScreen ? "screen" : "funnel"); const selectedScreenId = selectedScreen?.id ?? null; useEffect(() => { setActiveTab((previous) => { if (selectedScreenId) { return "screen"; } return previous === "screen" ? "funnel" : previous; }); }, [selectedScreenId]); const validation = useMemo(() => validateBuilderState(state), [state]); const screenValidationIssues = useMemo(() => { if (!selectedScreenId) { return [] as ValidationIssues; } return validation.issues.filter((issue) => issue.screenId === selectedScreenId); }, [selectedScreenId, validation]); const screenOptions = useMemo( () => state.screens.map((screen) => ({ id: screen.id, title: screen.title.text })), [state.screens] ); const handleMetaChange = (field: keyof typeof state.meta, value: string) => { dispatch({ type: "set-meta", payload: { [field]: value } }); }; const handleFirstScreenChange = (value: string) => { dispatch({ type: "set-meta", payload: { firstScreenId: value } }); }; const handleScreenIdChange = (currentId: string, newId: string) => { if (newId.trim() === "" || newId === currentId) { return; } // Обновляем ID экрана dispatch({ type: "update-screen", payload: { screenId: currentId, screen: { id: newId } } }); // Если это был первый экран в мета данных, обновляем и там if (state.meta.firstScreenId === currentId) { dispatch({ type: "set-meta", payload: { firstScreenId: newId } }); } }; const getScreenById = (screenId: string): BuilderScreen | undefined => state.screens.find((item) => item.id === screenId); const updateNavigation = ( screen: BuilderScreen, navigationUpdates: Partial = {} ) => { dispatch({ type: "update-navigation", payload: { screenId: screen.id, navigation: { defaultNextScreenId: navigationUpdates.defaultNextScreenId ?? screen.navigation?.defaultNextScreenId, rules: navigationUpdates.rules ?? screen.navigation?.rules ?? [], isEndScreen: navigationUpdates.isEndScreen ?? screen.navigation?.isEndScreen, }, }, }); }; const handleDefaultNextChange = (screenId: string, nextScreenId: string | "") => { const screen = getScreenById(screenId); if (!screen) { return; } updateNavigation(screen, { defaultNextScreenId: nextScreenId || undefined, }); }; const updateRules = (screenId: string, rules: NavigationRuleDefinition[]) => { const screen = getScreenById(screenId); if (!screen) { return; } updateNavigation(screen, { rules }); }; const handleRuleOperatorChange = ( screenId: string, index: number, operator: NavigationRuleDefinition["conditions"][0]["operator"] ) => { const screen = getScreenById(screenId); if (!screen) { return; } const rules = screen.navigation?.rules ?? []; const nextRules = rules.map((rule, ruleIndex) => ruleIndex === index ? { ...rule, conditions: rule.conditions.map((condition, conditionIndex) => conditionIndex === 0 ? { ...condition, operator, } : condition ), } : rule ); updateRules(screenId, nextRules); }; const handleRuleOptionToggle = (screenId: string, ruleIndex: number, optionId: string) => { const screen = getScreenById(screenId); if (!screen) { return; } const rules = screen.navigation?.rules ?? []; const nextRules = rules.map((rule, currentIndex) => { if (currentIndex !== ruleIndex) { return rule; } const [condition] = rule.conditions; const optionIds = new Set(condition.optionIds ?? []); if (optionIds.has(optionId)) { optionIds.delete(optionId); } else { optionIds.add(optionId); } return { ...rule, conditions: [ { ...condition, optionIds: Array.from(optionIds), }, ], }; }); updateRules(screenId, nextRules); }; const handleRuleNextScreenChange = (screenId: string, ruleIndex: number, nextScreenId: string) => { const screen = getScreenById(screenId); if (!screen) { return; } const rules = screen.navigation?.rules ?? []; const nextRules = rules.map((rule, currentIndex) => currentIndex === ruleIndex ? { ...rule, nextScreenId } : rule ); updateRules(screenId, nextRules); }; const handleAddRule = (screen: BuilderScreen) => { if (!isListScreen(screen)) { return; } const defaultCondition: NavigationRuleDefinition["conditions"][number] = { screenId: screen.id, operator: "includesAny", optionIds: screen.list.options.slice(0, 1).map((option) => option.id), }; const nextRules = [ ...(screen.navigation?.rules ?? []), { nextScreenId: state.screens[0]?.id ?? screen.id, conditions: [defaultCondition] }, ]; updateNavigation(screen, { rules: nextRules }); }; const handleRemoveRule = (screenId: string, ruleIndex: number) => { const screen = getScreenById(screenId); if (!screen) { return; } const rules = screen.navigation?.rules ?? []; const nextRules = rules.filter((_, index) => index !== ruleIndex); updateNavigation(screen, { rules: nextRules }); }; const handleDeleteScreen = (screenId: string) => { if (state.screens.length <= 1) { return; } dispatch({ type: "remove-screen", payload: { screenId } }); }; const handleTemplateUpdate = (screenId: string, updates: Partial) => { dispatch({ type: "update-screen", payload: { screenId, screen: updates as Partial, }, }); }; const handleVariantsChange = ( screenId: string, variants: ScreenVariantDefinition[] ) => { dispatch({ type: "update-screen", payload: { screenId, screen: { variants: variants.length > 0 ? variants : undefined, } as Partial, }, }); }; const selectedScreenIsListType = selectedScreen ? isListScreen(selectedScreen) : false; return (

Настройки

{activeTab === "funnel" ? (
handleMetaChange("id", event.target.value)} /> handleMetaChange("title", event.target.value)} /> handleMetaChange("description", event.target.value)} />
Всего экранов {state.screens.length}
{state.screens.map((screen, index) => ( {index + 1}. {screen.title.text} {screen.template} ))}
) : selectedScreen ? (
#{selectedScreen.id} {selectedScreen.template}
{state.screens.findIndex((screen) => screen.id === selectedScreen.id) + 1}/{state.screens.length}
handleScreenIdChange(selectedScreen.id, event.target.value)} />
handleTemplateUpdate(selectedScreen.id, updates)} />
handleVariantsChange(selectedScreen.id, variants)} />
{/* 🎯 ЧЕКБОКС ДЛЯ ФИНАЛЬНОГО ЭКРАНА */} {/* ОБЫЧНАЯ НАВИГАЦИЯ - показываем только если НЕ финальный экран */} {!selectedScreen.navigation?.isEndScreen && ( )}
{selectedScreenIsListType && !selectedScreen.navigation?.isEndScreen && (

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

{(selectedScreen.navigation?.rules ?? []).length === 0 && (
Правил пока нет
)} {(selectedScreen.navigation?.rules ?? []).map((rule, ruleIndex) => (
Правило {ruleIndex + 1}
{selectedScreen.template === "list" ? (
Варианты ответа
{selectedScreen.list.options.map((option) => { const condition = rule.conditions[0]; const isChecked = condition.optionIds?.includes(option.id) ?? false; return ( ); })}
) : (
Навигационные правила с вариантами ответа доступны только для экранов со списком.
)}
))}
)}

Удаление экрана нельзя отменить. Все связи с этим экраном будут потеряны.

) : (
Выберите экран в списке слева, чтобы настроить его параметры.
)}
); }