285 lines
11 KiB
TypeScript
285 lines
11 KiB
TypeScript
"use client";
|
||
|
||
import { useMemo } from "react";
|
||
import { AgeSelector } from "../AgeSelector";
|
||
import { ZodiacSelector } from "../ZodiacSelector";
|
||
import { EmailDomainSelector } from "../EmailDomainSelector";
|
||
import { ConditionTypeSelector } from "../ConditionTypeSelector";
|
||
import { UnleashConditionEditor } from "../UnleashConditionEditor";
|
||
import type { VariantConditionEditorProps } from "./types";
|
||
import type { BuilderScreen } from "@/lib/admin/builder/types";
|
||
import type { ListOptionDefinition } from "@/lib/funnel/types";
|
||
|
||
/**
|
||
* Редактор условия для варианта экрана
|
||
*/
|
||
export function VariantConditionEditor({
|
||
condition,
|
||
allScreens,
|
||
onChange,
|
||
}: VariantConditionEditorProps) {
|
||
// Находим выбранный экран (может быть ID экрана или storageKey для zodiac)
|
||
const selectedScreen = useMemo(() => {
|
||
// Сначала ищем по ID экрана
|
||
let screen = allScreens.find((s) => s.id === condition.screenId);
|
||
if (screen) return screen;
|
||
|
||
// Если не нашли, ищем date экран где storageKey === condition.screenId
|
||
screen = allScreens.find((s) => {
|
||
if (s.template === "date") {
|
||
const dateScreen = s as BuilderScreen & { template: "date"; dateInput?: { zodiac?: { enabled?: boolean; storageKey?: string } } };
|
||
const zodiacSettings = dateScreen.dateInput?.zodiac;
|
||
return zodiacSettings?.enabled && zodiacSettings.storageKey?.trim() === condition.screenId;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
return screen;
|
||
}, [allScreens, condition.screenId]);
|
||
|
||
// Определяем опции для условия (если это list экран)
|
||
// Собираем опции из базового экрана + из всех вариантов
|
||
const conditionOptions = useMemo<ListOptionDefinition[]>(() => {
|
||
if (!selectedScreen || selectedScreen.template !== "list") {
|
||
return [];
|
||
}
|
||
|
||
const listScreen = selectedScreen as BuilderScreen & {
|
||
template: "list";
|
||
list: { options: ListOptionDefinition[] };
|
||
variants?: Array<{ overrides?: { list?: { options?: ListOptionDefinition[] } } }>;
|
||
};
|
||
|
||
// Начинаем с базовых опций
|
||
const optionsMap = new Map<string, ListOptionDefinition>();
|
||
listScreen.list.options.forEach(opt => {
|
||
optionsMap.set(opt.id, opt);
|
||
});
|
||
|
||
// Добавляем опции из всех вариантов
|
||
if (listScreen.variants) {
|
||
listScreen.variants.forEach(variant => {
|
||
const variantOptions = variant.overrides?.list?.options;
|
||
if (variantOptions) {
|
||
variantOptions.forEach(opt => {
|
||
// Проверяем что опция валидна (имеет id)
|
||
if (opt && opt.id) {
|
||
// Добавляем или переопределяем опцию
|
||
optionsMap.set(opt.id, opt as ListOptionDefinition);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// Возвращаем все уникальные опции
|
||
return Array.from(optionsMap.values());
|
||
}, [selectedScreen]);
|
||
|
||
// Определяем, нужен ли специальный селектор
|
||
const showZodiacSelector = useMemo(() => {
|
||
if (!selectedScreen) return false;
|
||
// Проверяем специальный zodiac экран
|
||
if (selectedScreen.id === "zodiac-sign" || selectedScreen.id === "zodiac") {
|
||
return true;
|
||
}
|
||
// Проверяем date экран с zodiac.enabled и storageKey === condition.screenId
|
||
if (selectedScreen.template === "date") {
|
||
const dateScreen = selectedScreen as BuilderScreen & { template: "date"; dateInput?: { zodiac?: { enabled?: boolean; storageKey?: string } } };
|
||
const zodiacSettings = dateScreen.dateInput?.zodiac;
|
||
const storageKey = zodiacSettings?.storageKey?.trim();
|
||
// Показываем zodiac селектор если:
|
||
// 1. zodiac включен
|
||
// 2. storageKey совпадает с condition.screenId (т.е. пользователь выбрал этот storageKey)
|
||
return zodiacSettings?.enabled === true && storageKey === condition.screenId;
|
||
}
|
||
return false;
|
||
}, [selectedScreen, condition.screenId]);
|
||
|
||
const showEmailSelector = selectedScreen?.id === "email";
|
||
const showAgeSelector =
|
||
selectedScreen?.id === "age" || selectedScreen?.id === "crush-age" || selectedScreen?.id === "current-partner-age";
|
||
|
||
const conditionType = condition.conditionType ?? "options";
|
||
const isUnleashCondition = conditionType === "unleash";
|
||
|
||
// Обработчики для селекторов
|
||
const handleToggleOption = (optionId: string) => {
|
||
const currentIds = condition.optionIds || [];
|
||
const nextIds = currentIds.includes(optionId)
|
||
? currentIds.filter((id) => id !== optionId)
|
||
: [...currentIds, optionId];
|
||
onChange({ ...condition, optionIds: nextIds });
|
||
};
|
||
|
||
const handleAddCustomOption = (optionId: string) => {
|
||
const currentIds = condition.optionIds || [];
|
||
if (!currentIds.includes(optionId)) {
|
||
onChange({ ...condition, optionIds: [...currentIds, optionId] });
|
||
}
|
||
};
|
||
|
||
const handleConditionTypeChange = (newType: "options" | "values" | "unleash") => {
|
||
onChange({
|
||
...condition,
|
||
conditionType: newType,
|
||
// Очищаем поля при смене типа
|
||
optionIds: newType === "unleash" ? undefined : condition.optionIds,
|
||
values: newType === "unleash" ? undefined : condition.values,
|
||
unleashFlag: newType === "unleash" ? condition.unleashFlag : undefined,
|
||
unleashVariants: newType === "unleash" ? condition.unleashVariants : undefined,
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-3">
|
||
{/* Тип условия */}
|
||
<ConditionTypeSelector
|
||
condition={condition}
|
||
onChange={handleConditionTypeChange}
|
||
/>
|
||
|
||
{/* Unleash AB Test */}
|
||
{isUnleashCondition ? (
|
||
<UnleashConditionEditor condition={condition} onChange={onChange} />
|
||
) : (
|
||
<>
|
||
{/* Выбор экрана */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-muted-foreground mb-1">
|
||
Экран
|
||
</label>
|
||
<select
|
||
value={condition.screenId}
|
||
onChange={(e) => onChange({ ...condition, screenId: e.target.value, optionIds: [] })}
|
||
className="w-full h-9 rounded-md border border-border bg-background px-3 text-sm"
|
||
>
|
||
{allScreens.flatMap((screen) => {
|
||
const options = [];
|
||
|
||
// Для date экранов с zodiac добавляем отдельную опцию для storageKey
|
||
if (screen.template === "date") {
|
||
const dateScreen = screen as BuilderScreen & { template: "date"; dateInput?: { zodiac?: { enabled?: boolean; storageKey?: string } } };
|
||
const zodiacSettings = dateScreen.dateInput?.zodiac;
|
||
const storageKey = zodiacSettings?.storageKey?.trim();
|
||
if (zodiacSettings?.enabled && storageKey) {
|
||
options.push(
|
||
<option key={`${screen.id}-zodiac`} value={storageKey}>
|
||
{screen.id} (zodiac → {storageKey})
|
||
</option>
|
||
);
|
||
}
|
||
}
|
||
|
||
// Обычный экран (всегда)
|
||
options.push(
|
||
<option key={screen.id} value={screen.id}>
|
||
{screen.id} ({screen.template})
|
||
</option>
|
||
);
|
||
|
||
return options;
|
||
})}
|
||
</select>
|
||
</div>
|
||
|
||
{/* Оператор (только для list экранов с несколькими опциями) */}
|
||
{conditionOptions.length > 1 && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-muted-foreground mb-1">
|
||
Оператор
|
||
</label>
|
||
<select
|
||
value={condition.operator}
|
||
onChange={(e) =>
|
||
onChange({
|
||
...condition,
|
||
operator: e.target.value as "includesAny" | "includesAll",
|
||
})
|
||
}
|
||
className="w-full h-9 rounded-md border border-border bg-background px-3 text-sm"
|
||
>
|
||
<option value="includesAny">Любое из (OR)</option>
|
||
<option value="includesAll">Все из (AND)</option>
|
||
</select>
|
||
</div>
|
||
)}
|
||
|
||
{/* Zodiac Selector */}
|
||
{showZodiacSelector && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-muted-foreground mb-1">
|
||
Знаки зодиака
|
||
</label>
|
||
<ZodiacSelector
|
||
selectedValues={condition.optionIds || []}
|
||
onToggleValue={handleToggleOption}
|
||
onAddCustomValue={handleAddCustomOption}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* Email Domain Selector */}
|
||
{showEmailSelector && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-muted-foreground mb-1">
|
||
Email домены
|
||
</label>
|
||
<EmailDomainSelector
|
||
selectedValues={condition.optionIds || []}
|
||
onToggleValue={handleToggleOption}
|
||
onAddCustomValue={handleAddCustomOption}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* Age Selector */}
|
||
{showAgeSelector && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-muted-foreground mb-1">
|
||
Возраст
|
||
</label>
|
||
<AgeSelector
|
||
selectedValues={condition.optionIds || []}
|
||
onToggleValue={handleToggleOption}
|
||
onAddCustomValue={handleAddCustomOption}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* Опции для обычных list экранов */}
|
||
{!showZodiacSelector &&
|
||
!showEmailSelector &&
|
||
!showAgeSelector &&
|
||
conditionOptions.length > 0 && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-muted-foreground mb-1">
|
||
Значения
|
||
</label>
|
||
<div className="space-y-1 max-h-48 overflow-y-auto border border-border rounded-md p-2">
|
||
{conditionOptions.map((opt) => (
|
||
<label
|
||
key={opt.id}
|
||
className="flex items-center gap-2 p-2 rounded hover:bg-muted/50 cursor-pointer"
|
||
>
|
||
<input
|
||
type="checkbox"
|
||
checked={(condition.optionIds || []).includes(opt.id)}
|
||
onChange={() => handleToggleOption(opt.id)}
|
||
className="rounded border-border"
|
||
/>
|
||
<span className="text-sm">
|
||
{opt.emoji && <span className="mr-1">{opt.emoji}</span>}
|
||
{opt.label}
|
||
</span>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|