168 lines
5.8 KiB
TypeScript
168 lines
5.8 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { ChevronDown, ChevronRight } from "lucide-react";
|
||
import { TextInput } from "@/components/ui/TextInput/TextInput";
|
||
import { ImageUpload } from "@/components/admin/builder/forms/ImageUpload";
|
||
import { VariablesConfig } from "./VariablesConfig";
|
||
import { useBuilderState } from "@/lib/admin/builder/context";
|
||
import type { InfoScreenDefinition } from "@/lib/funnel/types";
|
||
import type { BuilderScreen } from "@/lib/admin/builder/types";
|
||
|
||
interface InfoScreenConfigProps {
|
||
screen: BuilderScreen & { template: "info" };
|
||
onUpdate: (updates: Partial<InfoScreenDefinition>) => void;
|
||
}
|
||
|
||
function CollapsibleSection({
|
||
title,
|
||
children,
|
||
defaultExpanded = false,
|
||
}: {
|
||
title: string;
|
||
children: React.ReactNode;
|
||
defaultExpanded?: boolean;
|
||
}) {
|
||
const storageKey = `info-section-${title.toLowerCase().replace(/\s+/g, '-')}`;
|
||
|
||
const [isExpanded, setIsExpanded] = useState(() => {
|
||
if (typeof window === 'undefined') return defaultExpanded;
|
||
|
||
const stored = sessionStorage.getItem(storageKey);
|
||
return stored !== null ? JSON.parse(stored) : defaultExpanded;
|
||
});
|
||
|
||
const handleToggle = () => {
|
||
const newExpanded = !isExpanded;
|
||
setIsExpanded(newExpanded);
|
||
|
||
if (typeof window !== 'undefined') {
|
||
sessionStorage.setItem(storageKey, JSON.stringify(newExpanded));
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-3">
|
||
<button
|
||
type="button"
|
||
onClick={handleToggle}
|
||
className="flex w-full items-center gap-2 text-left text-sm font-medium text-foreground hover:text-primary transition-colors"
|
||
>
|
||
{isExpanded ? (
|
||
<ChevronDown className="h-4 w-4" />
|
||
) : (
|
||
<ChevronRight className="h-4 w-4" />
|
||
)}
|
||
{title}
|
||
</button>
|
||
{isExpanded && <div className="ml-6 space-y-3">{children}</div>}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function InfoScreenConfig({ screen, onUpdate }: InfoScreenConfigProps) {
|
||
const infoScreen = screen as InfoScreenDefinition;
|
||
const state = useBuilderState();
|
||
|
||
// Получаем доступные экраны для настройки условий переменных
|
||
const availableScreens = state.screens;
|
||
|
||
|
||
const handleIconChange = <T extends keyof NonNullable<InfoScreenDefinition["icon"]>>(
|
||
field: T,
|
||
value: NonNullable<InfoScreenDefinition["icon"]>[T] | undefined
|
||
) => {
|
||
const baseIcon = infoScreen.icon ?? { type: "emoji", value: "✨", size: "lg" };
|
||
|
||
if (field === "value") {
|
||
if (!value) {
|
||
onUpdate({ icon: undefined });
|
||
} else {
|
||
onUpdate({ icon: { ...baseIcon, value } });
|
||
}
|
||
return;
|
||
}
|
||
|
||
// При изменении типа иконки сбрасываем значение
|
||
if (field === "type") {
|
||
const defaultValue = value === "emoji" ? "✨" : "";
|
||
onUpdate({ icon: { ...baseIcon, type: value as "emoji" | "image", value: defaultValue } });
|
||
return;
|
||
}
|
||
|
||
onUpdate({ icon: { ...baseIcon, [field]: value } });
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* Иконка */}
|
||
<CollapsibleSection title="Иконка" defaultExpanded={true}>
|
||
<div className="space-y-3">
|
||
<div className="space-y-2 text-xs">
|
||
<label className="flex flex-col gap-1 text-muted-foreground">
|
||
Тип иконки
|
||
<select
|
||
className="rounded-lg border border-border bg-background px-2 py-1"
|
||
value={infoScreen.icon?.type ?? "emoji"}
|
||
onChange={(event) => handleIconChange("type", event.target.value as "emoji" | "image")}
|
||
>
|
||
<option value="emoji">Emoji</option>
|
||
<option value="image">Изображение</option>
|
||
</select>
|
||
</label>
|
||
<label className="flex flex-col gap-1 text-muted-foreground">
|
||
Размер
|
||
<select
|
||
className="rounded-lg border border-border bg-background px-2 py-1"
|
||
value={infoScreen.icon?.size ?? "lg"}
|
||
onChange={(event) =>
|
||
handleIconChange("size", event.target.value as "sm" | "md" | "lg" | "xl")
|
||
}
|
||
>
|
||
<option value="sm">Маленький</option>
|
||
<option value="md">Средний</option>
|
||
<option value="lg">Большой</option>
|
||
<option value="xl">Огромный</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
|
||
{infoScreen.icon?.type === "image" ? (
|
||
<div>
|
||
<span className="text-xs font-medium text-muted-foreground mb-2 block">
|
||
Изображение иконки
|
||
</span>
|
||
<ImageUpload
|
||
currentValue={infoScreen.icon?.value}
|
||
onImageSelect={(url) => handleIconChange("value", url)}
|
||
onImageRemove={() => handleIconChange("value", undefined)}
|
||
funnelId={screen.id}
|
||
/>
|
||
</div>
|
||
) : (
|
||
<label className="flex flex-col gap-2 text-sm">
|
||
<span className="text-xs font-medium text-muted-foreground">
|
||
Emoji символ
|
||
</span>
|
||
<TextInput
|
||
placeholder="Например, ✨"
|
||
value={infoScreen.icon?.value ?? ""}
|
||
onChange={(event) => handleIconChange("value", event.target.value || undefined)}
|
||
/>
|
||
</label>
|
||
)}
|
||
</div>
|
||
</CollapsibleSection>
|
||
|
||
{/* Переменные */}
|
||
<CollapsibleSection title="Переменные">
|
||
<VariablesConfig
|
||
variables={infoScreen.variables}
|
||
onUpdate={(variables) => onUpdate({ variables })}
|
||
availableScreens={availableScreens}
|
||
/>
|
||
</CollapsibleSection>
|
||
</div>
|
||
);
|
||
}
|