import { useEffect, useMemo, useState } from "react"; import { Button, FeatureSettingsWindow, Select, SettingsCard, Switch, TextAreaField, TextField, WindowFooterActions, } from "@nodedc/ui-react"; import { cloneEnvironmentSettings, type EnvironmentBackground, type EnvironmentPage, type EnvironmentSettings, type EnvironmentSurfaceId, type UploadedEnvironmentMedia, } from "../core/environment/environmentSettings"; import { EnvironmentMediaPlaylistEditor } from "./EnvironmentMediaPlaylistEditor"; import { roots, workspaces, workspacesForRoot, } from "../productModel"; interface EnvironmentSettingsWindowProps { open: boolean; settings: EnvironmentSettings; state: "loading" | "ready" | "saving" | "error"; error: string | null; onClose: () => void; onSave: (settings: EnvironmentSettings) => Promise; onUpload: ( surfaceId: EnvironmentSurfaceId, itemId: string, file: File, ) => Promise; } function patchPage( draft: EnvironmentSettings, surfaceId: EnvironmentSurfaceId, patch: Partial, ): EnvironmentSettings { return { ...draft, pages: { ...draft.pages, [surfaceId]: { ...draft.pages[surfaceId], ...patch, }, }, }; } function patchBackground( draft: EnvironmentSettings, surfaceId: EnvironmentSurfaceId, patch: Partial, ): EnvironmentSettings { const page = draft.pages[surfaceId]; return patchPage(draft, surfaceId, { background: { ...page.background, ...patch, }, }); } export function EnvironmentSettingsWindow({ open, settings, state, error, onClose, onSave, onUpload, }: EnvironmentSettingsWindowProps) { const [draft, setDraft] = useState(() => cloneEnvironmentSettings(settings)); const [surfaceId, setSurfaceId] = useState("fleet"); const [uploading, setUploading] = useState(false); const [localError, setLocalError] = useState(null); useEffect(() => { if (!open) return; setDraft(cloneEnvironmentSettings(settings)); setLocalError(null); }, [open, settings]); const selectedPage = draft.pages[surfaceId]; const selectedBackground = selectedPage.background; const pageOptions = useMemo(() => [ { value: "home" as const, label: draft.pages.home.headerLabel, description: "Главная страница продукта", }, ...roots.map((root) => ({ value: root.id, label: draft.pages[root.id].headerLabel, description: `Стартовая страница раздела «${draft.pages[root.id].title}»`, })), ], [draft.pages]); const quickActionWorkspaces = useMemo( () => surfaceId === "home" ? workspaces.filter((workspace) => !workspace.internalOnly) : workspacesForRoot(surfaceId), [surfaceId], ); const quickActionOptions = useMemo(() => [ { value: "none", label: "Не показывать", description: "Кнопка скрыта на стартовом экране", }, ...quickActionWorkspaces.map((workspace) => ({ value: workspace.id, label: workspace.label, description: workspace.description, })), ], [quickActionWorkspaces]); const dirty = useMemo( () => JSON.stringify(draft) !== JSON.stringify(settings), [draft, settings], ); const busy = state === "saving" || uploading; const updatePage = (patch: Partial) => { setDraft((current) => patchPage(current, surfaceId, patch)); }; const save = async () => { const invalid = Object.entries(draft.pages).find(([, page]) => ( !page.headerLabel.trim() || !page.eyebrow.trim() || !page.title.trim() || !page.description.trim() )); if (invalid) { setLocalError("Название, надзаголовок, заголовок и описание не могут быть пустыми."); return; } if ( selectedPage.primaryWorkspaceId && selectedPage.primaryWorkspaceId === selectedPage.secondaryWorkspaceId ) { setLocalError("Быстрые кнопки должны вести на разные рабочие поверхности."); return; } const invalidMedia = Object.entries(draft.pages).find(([, page]) => ( (page.background.enabled && !page.background.items.length) || page.background.items.some((item) => { if (!item.url || !item.mediaKind) return true; if (item.source !== "url") return false; try { const parsed = new URL(item.url); return !["http:", "https:"].includes(parsed.protocol); } catch { return true; } }) )); if (invalidMedia) { setSurfaceId(invalidMedia[0] as EnvironmentSurfaceId); setLocalError( "Каждый элемент фона должен содержать загруженный файл или прямой HTTP(S) URL.", ); return; } setLocalError(null); try { await onSave({ ...draft, pages: Object.fromEntries( Object.entries(draft.pages).map(([id, page]) => [id, { ...page, headerLabel: page.headerLabel.trim(), eyebrow: page.eyebrow.trim(), title: page.title.trim(), description: page.description.trim(), }]), ) as EnvironmentSettings["pages"], }); onClose(); } catch (reason) { setLocalError(reason instanceof Error ? reason.message : "Не удалось сохранить настройки окружения."); } }; return ( undefined} onClose={onClose} footer={( )} >
{ if (enabled && !selectedBackground.items.length) { setLocalError("Сначала добавьте медиаконтент."); return; } setLocalError(null); setDraft((current) => patchBackground(current, surfaceId, { enabled })); }} /> )} >
Страница updatePage({ primaryWorkspaceId: value === "none" ? null : value, })} />
Кнопка 2