'use client' import React, { useState, useEffect } from 'react' import { TemplatesSelector } from './TemplatesSelector' import { ExportDataModal } from './ExportDataModal' import { ImportDataModal } from './ImportDataModal' import { designTemplates, DesignTemplate } from '../constants/designTemplates' import { useLocale } from '../contexts/LocaleContext' interface DesignSettings { id?: number template_id?: string 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 // Новые опции кастомизации group_overlay_enabled?: boolean group_overlay_color?: string group_overlay_opacity?: number show_groups_title?: boolean group_description_text_color?: string body_font_family?: string heading_font_family?: string // Новые поля для цветового оверлея кнопок ссылок link_overlay_enabled?: boolean link_overlay_color?: string link_overlay_opacity?: number } interface UserProfile { id: number username: string email: string full_name: string bio?: string avatar_url?: string } interface LinkItem { id: number title: string url: string icon_url?: string group: number } interface Group { id: number name: string description?: string icon_url?: string background_image_url?: string is_public?: boolean is_favorite?: boolean links: LinkItem[] } interface CustomizationPanelProps { isOpen: boolean onClose: () => void onSettingsUpdate: (settings: DesignSettings) => void user?: UserProfile | null groups?: Group[] onDataUpdate?: () => void } export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate, user, groups = [], onDataUpdate }: CustomizationPanelProps) { const { t } = useLocale() 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' | 'templates' | 'advanced' | 'data'>('templates') const [backgroundImageFile, setBackgroundImageFile] = useState(null) // Состояния для модалов экспорта/импорта const [showExportModal, setShowExportModal] = useState(false) const [showImportModal, setShowImportModal] = useState(false) useEffect(() => { if (isOpen) { loadSettings() } }, [isOpen]) const loadSettings = async () => { try { const token = localStorage.getItem('token') const API = process.env.NEXT_PUBLIC_API_URL || 'https://links.shareon.kr' 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 || 'https://links.shareon.kr' // Если есть новый файл фоновой картинки, отправляем через FormData if (backgroundImageFile) { const formData = new FormData() // Добавляем все настройки formData.append('template_id', settings.template_id || '') 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('group_overlay_enabled', (settings.group_overlay_enabled || false).toString()) formData.append('group_overlay_color', settings.group_overlay_color || '#000000') formData.append('group_overlay_opacity', (settings.group_overlay_opacity || 0.3).toString()) formData.append('show_groups_title', (settings.show_groups_title !== false).toString()) formData.append('group_description_text_color', settings.group_description_text_color || '#666666') formData.append('body_font_family', settings.body_font_family || 'sans-serif') formData.append('heading_font_family', settings.heading_font_family || 'sans-serif') formData.append('link_overlay_enabled', (settings.link_overlay_enabled || false).toString()) formData.append('link_overlay_color', settings.link_overlay_color || '#000000') formData.append('link_overlay_opacity', (settings.link_overlay_opacity || 0.2).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 = { template_id: settings.template_id, 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', 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, group_overlay_enabled: settings.group_overlay_enabled || false, group_overlay_color: settings.group_overlay_color || '#000000', group_overlay_opacity: settings.group_overlay_opacity || 0.3, show_groups_title: settings.show_groups_title !== false, group_description_text_color: settings.group_description_text_color || '#666666', body_font_family: settings.body_font_family || 'sans-serif', heading_font_family: settings.heading_font_family || 'sans-serif', link_overlay_enabled: settings.link_overlay_enabled || false, link_overlay_color: settings.link_overlay_color || '#000000', link_overlay_opacity: settings.link_overlay_opacity || 0.2 } 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 })) } const handleTemplateSelect = (template: DesignTemplate) => { setSettings(prev => ({ ...prev, ...template.settings, id: prev.id, // Сохраняем оригинальный ID template_id: template.id // Добавляем ID шаблона для отслеживания })) } // Определяем текущий шаблон const getCurrentTemplateId = () => { // Если есть сохраненный template_id if ((settings as any).template_id) { return (settings as any).template_id } // Или пытаемся определить по совпадению настроек for (const template of designTemplates) { if ( template.settings.theme_color === settings.theme_color && template.settings.background_color === settings.dashboard_background_color && template.settings.dashboard_layout === settings.dashboard_layout ) { return template.id } } return undefined } if (!isOpen) return null return (
{t('customization.title')}
{/* Вкладки */}
{/* Содержимое вкладок */}
{/* Вкладка: Макет */} {activeTab === 'layout' && (
{[ { value: 'list', labelKey: 'customization.layout.list', icon: 'bi-list-ul', descriptionKey: 'customization.layout.listDescription' }, { value: 'grid', labelKey: 'customization.layout.grid', icon: 'bi-grid-3x3', descriptionKey: 'customization.layout.gridDescription' }, { value: 'cards', labelKey: 'customization.layout.cards', icon: 'bi-card-heading', descriptionKey: 'customization.layout.cardsDescription' }, { value: 'compact', labelKey: 'customization.layout.compact', icon: 'bi-layout-text-sidebar', descriptionKey: 'customization.layout.compactDescription' }, { value: 'sidebar', labelKey: 'customization.layout.sidebar', icon: 'bi-layout-sidebar', descriptionKey: 'customization.layout.sidebarDescription' }, { value: 'masonry', labelKey: 'customization.layout.masonry', icon: 'bi-bricks', descriptionKey: 'customization.layout.masonryDescription' }, { value: 'timeline', labelKey: 'customization.layout.timeline', icon: 'bi-clock-history', descriptionKey: 'customization.layout.timelineDescription' }, { value: 'magazine', labelKey: 'customization.layout.magazine', icon: 'bi-newspaper', descriptionKey: 'customization.layout.magazineDescription' }, { value: 'test-list', labelKey: 'customization.layout.testList', icon: 'bi-list-check', descriptionKey: 'customization.layout.testListDescription' } ].map((layout) => (
handleChange('dashboard_layout', layout.value)} >
{t(layout.labelKey)}

{t(layout.descriptionKey)}

{settings.dashboard_layout === layout.value && (
Выбрано
)}
))}
{t('customization.layout.tip')} {t('customization.layout.tipText')} Каждый стиль имеет свои преимущества в зависимости от количества ссылок и их типа.
)} {/* Вкладка: Цвета */} {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) } }} />
{t('customization.colors.backgroundImageHelp')}
{settings.background_image_url && (
{t('customization.colors.currentBackgroundAlt')}
)} {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' && (
{t('customization.groups.displaySettings')}
handleChange('groups_default_expanded', e.target.checked)} />
handleChange('show_group_icons', e.target.checked)} />
handleChange('show_link_icons', e.target.checked)} />
{/* Новые настройки */}
handleChange('show_groups_title', e.target.checked)} />
handleChange('group_description_text_color', e.target.value)} /> handleChange('group_description_text_color', e.target.value)} />
{/* Перекрытие групп цветом */}
{t('customization.colors.groupOverlay')}
handleChange('group_overlay_enabled', e.target.checked)} />
{settings.group_overlay_enabled && ( <>
handleChange('group_overlay_color', e.target.value)} title={t('customization.colors.chooseOverlayColor')} /> handleChange('group_overlay_color', e.target.value)} placeholder="#000000" title={t('customization.colors.hexColorCode')} />
handleChange('group_overlay_opacity', parseFloat(e.target.value))} title="Настройка прозрачности перекрытия" />
{/* Preview */}
Пример группы
)}
{t('customization.advanced.individualGroupSettings')}
Чтобы настроить конкретную группу (публичность, избранное, разворачивание), используйте кнопку редактирования рядом с названием группы в основном списке.
{/* Секция цветового оверлея кнопок ссылок */}
{t('customization.colors.linkOverlay')}
{ console.log('Link overlay enabled:', e.target.checked) handleChange('link_overlay_enabled', e.target.checked) }} />
{settings.link_overlay_enabled && ( <>
{ console.log('Link overlay color:', e.target.value) handleChange('link_overlay_color', e.target.value) }} />
{ const value = parseFloat(e.target.value) console.log('Link overlay opacity:', value) handleChange('link_overlay_opacity', value) }} />
Кнопка ссылки
)}
)} {/* Вкладка: Шаблоны */} {activeTab === 'templates' && (
)} {/* Вкладка: Дополнительно */} {activeTab === 'advanced' && (
{t('customization.advanced.fontSettings')}

{t('customization.advanced.additionalSettings')}