'use client' import React, { useState, useEffect } from 'react' interface DesignSettings { id?: number theme_color: string background_image?: string background_image_url?: string dashboard_layout: 'sidebar' | 'grid' | 'list' | 'cards' | 'compact' | 'masonry' | 'timeline' | 'magazine' groups_default_expanded: boolean show_group_icons: boolean show_link_icons: boolean dashboard_background_color: string font_family: string custom_css: string group_text_color?: string link_text_color?: string header_text_color?: string cover_overlay_enabled?: boolean cover_overlay_color?: string cover_overlay_opacity?: number } interface CustomizationPanelProps { isOpen: boolean onClose: () => void onSettingsUpdate: (settings: DesignSettings) => void } export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: CustomizationPanelProps) { const [settings, setSettings] = useState({ theme_color: '#ffffff', dashboard_layout: 'list', groups_default_expanded: true, show_group_icons: true, show_link_icons: true, dashboard_background_color: '#f8f9fa', font_family: 'sans-serif', custom_css: '', group_text_color: '#333333', link_text_color: '#666666', header_text_color: '#000000' }) const [loading, setLoading] = useState(false) const [activeTab, setActiveTab] = useState<'layout' | 'colors' | 'groups' | 'advanced'>('layout') const [backgroundImageFile, setBackgroundImageFile] = useState(null) useEffect(() => { if (isOpen) { loadSettings() } }, [isOpen]) const loadSettings = async () => { try { const token = localStorage.getItem('token') const API = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000' const response = await fetch(`${API}/api/customization/settings/`, { headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { const data = await response.json() setSettings(data) } } catch (error) { console.error('Error loading settings:', error) } } const saveSettings = async () => { setLoading(true) try { const token = localStorage.getItem('token') const API = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000' // Если есть новый файл фоновой картинки, отправляем через FormData if (backgroundImageFile) { const formData = new FormData() // Добавляем все настройки formData.append('theme_color', settings.theme_color) formData.append('dashboard_layout', settings.dashboard_layout) formData.append('groups_default_expanded', settings.groups_default_expanded.toString()) formData.append('show_group_icons', settings.show_group_icons.toString()) formData.append('show_link_icons', settings.show_link_icons.toString()) formData.append('dashboard_background_color', settings.dashboard_background_color) formData.append('font_family', settings.font_family) formData.append('custom_css', settings.custom_css) formData.append('header_text_color', settings.header_text_color || '#000000') formData.append('group_text_color', settings.group_text_color || '#333333') formData.append('link_text_color', settings.link_text_color || '#666666') formData.append('cover_overlay_enabled', (settings.cover_overlay_enabled || false).toString()) formData.append('cover_overlay_color', settings.cover_overlay_color || '#000000') formData.append('cover_overlay_opacity', (settings.cover_overlay_opacity || 0.3).toString()) formData.append('background_image', backgroundImageFile) const response = await fetch(`${API}/api/customization/settings/`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}` }, body: formData }) if (response.ok) { const updatedSettings = await response.json() onSettingsUpdate(updatedSettings) setBackgroundImageFile(null) // Сбрасываем выбранный файл onClose() } else { const errorData = await response.json() console.error('Server error:', errorData) } } else { // Если файл не выбран, отправляем только JSON настройки (картинка остается прежней) const editableSettings = { theme_color: settings.theme_color, dashboard_layout: settings.dashboard_layout, groups_default_expanded: settings.groups_default_expanded, show_group_icons: settings.show_group_icons, show_link_icons: settings.show_link_icons, dashboard_background_color: settings.dashboard_background_color, font_family: settings.font_family, custom_css: settings.custom_css, header_text_color: settings.header_text_color || '#000000', header_text_color: settings.header_text_color || '#000000', group_text_color: settings.group_text_color || '#333333', link_text_color: settings.link_text_color || '#666666', cover_overlay_enabled: settings.cover_overlay_enabled || false, cover_overlay_color: settings.cover_overlay_color || '#000000', cover_overlay_opacity: settings.cover_overlay_opacity || 0.3 } const response = await fetch(`${API}/api/customization/settings/`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(editableSettings) }) if (response.ok) { const updatedSettings = await response.json() onSettingsUpdate(updatedSettings) onClose() } else { const errorData = await response.json() console.error('Server error:', errorData) } } } catch (error) { console.error('Error saving settings:', error) } finally { setLoading(false) } } const handleChange = (field: keyof DesignSettings, value: any) => { setSettings(prev => ({ ...prev, [field]: value })) } if (!isOpen) return null return (
Настройки дашборда
{/* Вкладки */}
{/* Содержимое вкладок */}
{/* Вкладка: Макет */} {activeTab === 'layout' && (
{[ { value: 'list', label: 'Список', icon: 'bi-list-ul', description: 'Классический вертикальный список' }, { value: 'grid', label: 'Сетка', icon: 'bi-grid-3x3', description: 'Равномерная сетка карточек' }, { value: 'cards', label: 'Карточки', icon: 'bi-card-heading', description: 'Большие информативные карточки' }, { value: 'compact', label: 'Компактный', icon: 'bi-layout-text-sidebar', description: 'Компактное отображение без отступов' }, { value: 'sidebar', label: 'Боковая панель', icon: 'bi-layout-sidebar', description: 'Навигация в боковой панели' }, { value: 'masonry', label: 'Кладка', icon: 'bi-bricks', description: 'Динамическая сетка разной высоты' }, { value: 'timeline', label: 'Лента времени', icon: 'bi-clock-history', description: 'Хронологическое отображение' }, { value: 'magazine', label: 'Журнальный', icon: 'bi-newspaper', description: 'Стиль журнала с крупными изображениями' } ].map((layout) => (
handleChange('dashboard_layout', layout.value)} >
{layout.label}

{layout.description}

{settings.dashboard_layout === layout.value && (
Выбрано
)}
))}
Совет: Попробуйте разные макеты, чтобы найти наиболее подходящий для вашего контента. Каждый стиль имеет свои преимущества в зависимости от количества ссылок и их типа.
)} {/* Вкладка: Цвета */} {activeTab === 'colors' && (
handleChange('theme_color', e.target.value)} /> handleChange('theme_color', e.target.value)} />
handleChange('dashboard_background_color', e.target.value)} /> handleChange('dashboard_background_color', e.target.value)} />
{ const file = e.target.files?.[0] if (file) { setBackgroundImageFile(file) } }} />
Выберите изображение для фона (JPG, PNG, GIF). Если не выбрано - текущее изображение останется без изменений.
{settings.background_image_url && (
Текущий фон
)} {backgroundImageFile && (
{backgroundImageFile.name}
)}
handleChange('header_text_color', e.target.value)} /> handleChange('header_text_color', e.target.value)} />
handleChange('group_text_color', e.target.value)} /> handleChange('group_text_color', e.target.value)} />
handleChange('link_text_color', e.target.value)} /> handleChange('link_text_color', e.target.value)} />
)} {/* Вкладка: Группы */} {activeTab === 'groups' && (
Настройки отображения групп
handleChange('groups_default_expanded', e.target.checked)} />
handleChange('show_group_icons', e.target.checked)} />
handleChange('show_link_icons', e.target.checked)} />
Настройки отдельных групп
Чтобы настроить конкретную группу (публичность, избранное, разворачивание), используйте кнопку редактирования рядом с названием группы в основном списке.
)} {/* Вкладка: Дополнительно */} {activeTab === 'advanced' && (