Исправлена подсветка шаблонов, добавлен макет тестовый список, исправлены проблемы со шрифтами
- Добавлено поле template_id в модель DesignSettings - Исправлена логика подсветки выбранного шаблона в TemplatesSelector - Добавлен новый макет 'test-list' - полный несворачиваемый список - Обновлены шрифты с поддержкой CSS переменных - Создан CSS модуль для тестового списка - Обеспечена совместимость иконок во всех макетах
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-09 02:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('customization', '0007_designsettings_body_font_family_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='designsettings',
|
||||||
|
name='template_id',
|
||||||
|
field=models.CharField(blank=True, help_text='ID выбранного дизайн-шаблона', max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='designsettings',
|
||||||
|
name='dashboard_layout',
|
||||||
|
field=models.CharField(choices=[('sidebar', 'Боковая панель'), ('grid', 'Сетка'), ('list', 'Список'), ('cards', 'Карточки'), ('compact', 'Компактный'), ('masonry', 'Кладка'), ('timeline', 'Временная линия'), ('magazine', 'Журнальный'), ('test-list', 'Тестовый список')], default='list', help_text='Стиль отображения дашборда', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -38,6 +38,7 @@ class DesignSettings(models.Model):
|
|||||||
('masonry', 'Кладка'),
|
('masonry', 'Кладка'),
|
||||||
('timeline', 'Временная линия'),
|
('timeline', 'Временная линия'),
|
||||||
('magazine', 'Журнальный'),
|
('magazine', 'Журнальный'),
|
||||||
|
('test-list', 'Тестовый список'),
|
||||||
],
|
],
|
||||||
default='list',
|
default='list',
|
||||||
help_text='Стиль отображения дашборда'
|
help_text='Стиль отображения дашборда'
|
||||||
@@ -140,6 +141,14 @@ class DesignSettings(models.Model):
|
|||||||
help_text='Шрифт для заголовков'
|
help_text='Шрифт для заголовков'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ID выбранного шаблона
|
||||||
|
template_id = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text='ID выбранного дизайн-шаблона'
|
||||||
|
)
|
||||||
|
|
||||||
updated_at = models.DateTimeField(
|
updated_at = models.DateTimeField(
|
||||||
auto_now=True,
|
auto_now=True,
|
||||||
help_text='Дата и время последнего изменения'
|
help_text='Дата и время последнего изменения'
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class DesignSettingsSerializer(serializers.ModelSerializer):
|
|||||||
model = DesignSettings
|
model = DesignSettings
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
|
'template_id',
|
||||||
'theme_color',
|
'theme_color',
|
||||||
'background_image',
|
'background_image',
|
||||||
'background_image_url',
|
'background_image_url',
|
||||||
@@ -120,7 +121,7 @@ class DesignSettingsSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Валидация типа макета дашборда
|
Валидация типа макета дашборда
|
||||||
"""
|
"""
|
||||||
valid_layouts = ['sidebar', 'grid', 'list', 'cards', 'compact', 'masonry', 'timeline', 'magazine']
|
valid_layouts = ['sidebar', 'grid', 'list', 'cards', 'compact', 'masonry', 'timeline', 'magazine', 'test-list']
|
||||||
if value not in valid_layouts:
|
if value not in valid_layouts:
|
||||||
raise serializers.ValidationError(f'Макет должен быть одним из: {", ".join(valid_layouts)}')
|
raise serializers.ValidationError(f'Макет должен быть одним из: {", ".join(valid_layouts)}')
|
||||||
return value
|
return value
|
||||||
@@ -252,6 +253,14 @@ class DesignSettingsSerializer(serializers.ModelSerializer):
|
|||||||
raise serializers.ValidationError('Название шрифта слишком длинное')
|
raise serializers.ValidationError('Название шрифта слишком длинное')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def validate_template_id(self, value):
|
||||||
|
"""
|
||||||
|
Валидация ID шаблона
|
||||||
|
"""
|
||||||
|
if value and len(value) > 50:
|
||||||
|
raise serializers.ValidationError('ID шаблона слишком длинный')
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class PublicDesignSettingsSerializer(serializers.ModelSerializer):
|
class PublicDesignSettingsSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/* Стили для макета "Тестовый список" */
|
||||||
|
.testListLayout {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
/* Применяем пользовательские шрифты через CSS переменные */
|
||||||
|
font-family: var(--user-font-family, 'Inter', sans-serif);
|
||||||
|
}
|
||||||
|
|
||||||
|
.testListLayout h5 {
|
||||||
|
font-family: var(--user-heading-font-family, var(--user-font-family, 'Inter', sans-serif));
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkGroup {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader {
|
||||||
|
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-family: var(--user-heading-font-family, var(--user-font-family, 'Inter', sans-serif));
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupLinks {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkItem {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #475569;
|
||||||
|
font-family: var(--user-body-font-family, var(--user-font-family, 'Inter', sans-serif));
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkItem:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-color: #6366f1;
|
||||||
|
transform: translateX(4px);
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkTitle {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #334155;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkDescription {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-left: auto;
|
||||||
|
opacity: 0.8;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Для групп с иконками в заголовке */
|
||||||
|
.groupHeader .linkIcon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.testListLayout {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupHeader {
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkItem {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkDescription {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Темная тема поддержка */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.linkItem {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkItem:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-color: #8b5cf6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkTitle {
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkDescription {
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import Link from 'next/link'
|
|||||||
import { useEffect, useState, Fragment } from 'react'
|
import { useEffect, useState, Fragment } from 'react'
|
||||||
import FontLoader from '../components/FontLoader'
|
import FontLoader from '../components/FontLoader'
|
||||||
import ExpandableGroup from '../components/ExpandableGroup'
|
import ExpandableGroup from '../components/ExpandableGroup'
|
||||||
|
import styles from './TestListLayout.module.css'
|
||||||
|
|
||||||
interface LinkItem {
|
interface LinkItem {
|
||||||
id: number
|
id: number
|
||||||
@@ -836,9 +837,79 @@ export default function UserPage({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Тестовый список - все группы и ссылки в одном списке
|
||||||
|
const renderTestListLayout = () => (
|
||||||
|
<div className={styles.testListLayout}>
|
||||||
|
{(designSettings.show_groups_title !== false) && (
|
||||||
|
<h5 className="mb-4" style={{
|
||||||
|
color: designSettings.header_text_color || designSettings.theme_color,
|
||||||
|
fontFamily: designSettings.heading_font_family || designSettings.font_family
|
||||||
|
}}>
|
||||||
|
Все группы и ссылки
|
||||||
|
</h5>
|
||||||
|
)}
|
||||||
|
{data!.groups.map((group) => (
|
||||||
|
<div key={group.id} className={styles.linkGroup}>
|
||||||
|
{/* Заголовок группы */}
|
||||||
|
<div className={styles.groupHeader}>
|
||||||
|
{group.icon_url && designSettings.show_group_icons && (
|
||||||
|
<img
|
||||||
|
src={group.icon_url}
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className={styles.linkIcon}
|
||||||
|
alt={group.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className={styles.linkTitle}>{group.name}</span>
|
||||||
|
{group.is_favorite && (
|
||||||
|
<i className="bi bi-star-fill text-warning ms-2"></i>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Описание группы если есть */}
|
||||||
|
{group.description && (
|
||||||
|
<p className={`${styles.linkDescription} mb-3 ps-2`}>
|
||||||
|
{group.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Ссылки группы */}
|
||||||
|
<div className={styles.groupLinks}>
|
||||||
|
{group.links.map((link) => (
|
||||||
|
<a
|
||||||
|
key={link.id}
|
||||||
|
href={link.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={styles.linkItem}
|
||||||
|
>
|
||||||
|
{link.icon_url && designSettings.show_link_icons && (
|
||||||
|
<img
|
||||||
|
src={link.icon_url}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className={styles.linkIcon}
|
||||||
|
alt={link.title}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className={styles.linkTitle}>{link.title}</span>
|
||||||
|
{link.description && (
|
||||||
|
<small className={styles.linkDescription}>{link.description}</small>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
// Основная функция рендеринга групп в зависимости от выбранного макета
|
// Основная функция рендеринга групп в зависимости от выбранного макета
|
||||||
const renderGroupsLayout = () => {
|
const renderGroupsLayout = () => {
|
||||||
switch (designSettings.dashboard_layout) {
|
switch (designSettings.dashboard_layout) {
|
||||||
|
case 'test-list':
|
||||||
|
return renderTestListLayout()
|
||||||
case 'list':
|
case 'list':
|
||||||
return renderListLayout()
|
return renderListLayout()
|
||||||
case 'grid':
|
case 'grid':
|
||||||
@@ -888,8 +959,17 @@ export default function UserPage({
|
|||||||
backgroundAttachment: 'fixed',
|
backgroundAttachment: 'fixed',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
paddingTop: '2rem', // отступ сверху для рамки фона
|
paddingTop: '2rem', // отступ сверху для рамки фона
|
||||||
paddingBottom: '2rem' // отступ снизу для рамки фона
|
paddingBottom: '2rem', // отступ снизу для рамки фона
|
||||||
}
|
// CSS переменные для использования в стилях
|
||||||
|
'--user-font-family': designSettings.font_family,
|
||||||
|
'--user-heading-font-family': designSettings.heading_font_family || designSettings.font_family,
|
||||||
|
'--user-body-font-family': designSettings.body_font_family || designSettings.font_family,
|
||||||
|
'--user-theme-color': designSettings.theme_color,
|
||||||
|
'--user-header-text-color': designSettings.header_text_color || designSettings.theme_color,
|
||||||
|
'--user-group-text-color': designSettings.group_text_color || '#333333',
|
||||||
|
'--user-link-text-color': designSettings.link_text_color || '#666666',
|
||||||
|
'--user-group-description-text-color': designSettings.group_description_text_color || '#666666'
|
||||||
|
} as React.CSSProperties
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { designTemplates, DesignTemplate } from '../constants/designTemplates'
|
|||||||
|
|
||||||
interface DesignSettings {
|
interface DesignSettings {
|
||||||
id?: number
|
id?: number
|
||||||
|
template_id?: string
|
||||||
theme_color: string
|
theme_color: string
|
||||||
background_image?: string
|
background_image?: string
|
||||||
background_image_url?: string
|
background_image_url?: string
|
||||||
@@ -92,6 +93,7 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
// Добавляем все настройки
|
// Добавляем все настройки
|
||||||
|
formData.append('template_id', settings.template_id || '')
|
||||||
formData.append('theme_color', settings.theme_color)
|
formData.append('theme_color', settings.theme_color)
|
||||||
formData.append('dashboard_layout', settings.dashboard_layout)
|
formData.append('dashboard_layout', settings.dashboard_layout)
|
||||||
formData.append('groups_default_expanded', settings.groups_default_expanded.toString())
|
formData.append('groups_default_expanded', settings.groups_default_expanded.toString())
|
||||||
@@ -135,6 +137,7 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
|
|||||||
} else {
|
} else {
|
||||||
// Если файл не выбран, отправляем только JSON настройки (картинка остается прежней)
|
// Если файл не выбран, отправляем только JSON настройки (картинка остается прежней)
|
||||||
const editableSettings = {
|
const editableSettings = {
|
||||||
|
template_id: settings.template_id,
|
||||||
theme_color: settings.theme_color,
|
theme_color: settings.theme_color,
|
||||||
dashboard_layout: settings.dashboard_layout,
|
dashboard_layout: settings.dashboard_layout,
|
||||||
groups_default_expanded: settings.groups_default_expanded,
|
groups_default_expanded: settings.groups_default_expanded,
|
||||||
@@ -144,7 +147,6 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
|
|||||||
font_family: settings.font_family,
|
font_family: settings.font_family,
|
||||||
custom_css: settings.custom_css,
|
custom_css: settings.custom_css,
|
||||||
header_text_color: settings.header_text_color || '#000000',
|
header_text_color: settings.header_text_color || '#000000',
|
||||||
header_text_color: settings.header_text_color || '#000000',
|
|
||||||
group_text_color: settings.group_text_color || '#333333',
|
group_text_color: settings.group_text_color || '#333333',
|
||||||
link_text_color: settings.link_text_color || '#666666',
|
link_text_color: settings.link_text_color || '#666666',
|
||||||
cover_overlay_enabled: settings.cover_overlay_enabled || false,
|
cover_overlay_enabled: settings.cover_overlay_enabled || false,
|
||||||
@@ -195,10 +197,31 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
|
|||||||
setSettings(prev => ({
|
setSettings(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
...template.settings,
|
...template.settings,
|
||||||
id: prev.id // Сохраняем оригинальный ID
|
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
|
if (!isOpen) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -328,6 +351,12 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
|
|||||||
label: 'Журнальный',
|
label: 'Журнальный',
|
||||||
icon: 'bi-newspaper',
|
icon: 'bi-newspaper',
|
||||||
description: 'Стиль журнала с крупными изображениями'
|
description: 'Стиль журнала с крупными изображениями'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'test-list',
|
||||||
|
label: 'Тестовый список',
|
||||||
|
icon: 'bi-list-check',
|
||||||
|
description: 'Полный несворачиваемый список всех групп и ссылок'
|
||||||
}
|
}
|
||||||
].map((layout) => (
|
].map((layout) => (
|
||||||
<div key={layout.value} className="col-md-6 col-lg-4">
|
<div key={layout.value} className="col-md-6 col-lg-4">
|
||||||
@@ -683,7 +712,7 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
|
|||||||
<div className="tab-pane fade show active">
|
<div className="tab-pane fade show active">
|
||||||
<TemplatesSelector
|
<TemplatesSelector
|
||||||
onTemplateSelect={handleTemplateSelect}
|
onTemplateSelect={handleTemplateSelect}
|
||||||
currentTemplate={undefined} // Можно добавить определение текущего шаблона
|
currentTemplate={getCurrentTemplateId()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { designTemplates, DesignTemplate } from '../constants/designTemplates'
|
import { designTemplates, DesignTemplate } from '../constants/designTemplates'
|
||||||
import styles from './TemplatesSelector.module.css'
|
|
||||||
|
|
||||||
interface TemplatesSelectorProps {
|
interface TemplatesSelectorProps {
|
||||||
onTemplateSelect: (template: DesignTemplate) => void
|
onTemplateSelect: (template: DesignTemplate) => void
|
||||||
@@ -10,13 +9,16 @@ interface TemplatesSelectorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TemplatesSelector({ onTemplateSelect, currentTemplate }: TemplatesSelectorProps) {
|
export function TemplatesSelector({ onTemplateSelect, currentTemplate }: TemplatesSelectorProps) {
|
||||||
const handleTemplateClick = (template: DesignTemplate) => {
|
const handleTemplateClick = (template: DesignTemplate, event: React.MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
console.log('Template clicked:', template.name)
|
console.log('Template clicked:', template.name)
|
||||||
|
console.log('onTemplateSelect function:', onTemplateSelect)
|
||||||
onTemplateSelect(template)
|
onTemplateSelect(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.templateSelector}>
|
<div>
|
||||||
<h6 className="mb-3">
|
<h6 className="mb-3">
|
||||||
<i className="bi bi-palette me-2"></i>
|
<i className="bi bi-palette me-2"></i>
|
||||||
Готовые шаблоны
|
Готовые шаблоны
|
||||||
@@ -24,90 +26,72 @@ export function TemplatesSelector({ onTemplateSelect, currentTemplate }: Templat
|
|||||||
<div className="row g-3">
|
<div className="row g-3">
|
||||||
{designTemplates.map((template) => (
|
{designTemplates.map((template) => (
|
||||||
<div key={template.id} className="col-md-6 col-lg-4">
|
<div key={template.id} className="col-md-6 col-lg-4">
|
||||||
<div
|
<button
|
||||||
className={`${styles.templateCard} card h-100 ${currentTemplate === template.id ? `${styles.selected} border-primary` : 'border-secondary'}`}
|
className={`btn w-100 h-100 p-0 border ${currentTemplate === template.id ? 'border-primary' : 'border-secondary'}`}
|
||||||
onClick={() => handleTemplateClick(template)}
|
onClick={(e) => handleTemplateClick(template, e)}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<div
|
<div className="card h-100">
|
||||||
className={styles.templatePreview}
|
<div
|
||||||
style={{
|
className="card-img-top"
|
||||||
background: template.settings.background_color,
|
style={{
|
||||||
}}
|
height: '120px',
|
||||||
>
|
background: template.settings.background_color || '#ffffff',
|
||||||
{/* Мини-превью дизайна */}
|
position: 'relative'
|
||||||
<div className={styles.previewContent}>
|
}}
|
||||||
<div>
|
>
|
||||||
<div
|
{/* Простое превью */}
|
||||||
className={styles.previewHeader}
|
<div className="p-3 h-100 d-flex flex-column justify-content-between">
|
||||||
style={{
|
<div>
|
||||||
backgroundColor: template.settings.header_text_color,
|
<div
|
||||||
}}
|
style={{
|
||||||
></div>
|
height: '20px',
|
||||||
<div
|
backgroundColor: template.settings.header_text_color || '#000',
|
||||||
className={styles.previewSubtitle}
|
borderRadius: '4px',
|
||||||
style={{
|
opacity: 0.8,
|
||||||
backgroundColor: template.settings.group_text_color,
|
width: '70%',
|
||||||
}}
|
marginBottom: '8px'
|
||||||
></div>
|
}}
|
||||||
</div>
|
></div>
|
||||||
<div>
|
<div
|
||||||
<div
|
style={{
|
||||||
className={styles.previewButton}
|
height: '12px',
|
||||||
style={{
|
backgroundColor: template.settings.group_text_color || '#666',
|
||||||
backgroundColor: template.settings.theme_color,
|
borderRadius: '2px',
|
||||||
}}
|
opacity: 0.6,
|
||||||
></div>
|
width: '50%'
|
||||||
<div
|
}}
|
||||||
className={styles.previewText}
|
></div>
|
||||||
style={{
|
</div>
|
||||||
backgroundColor: template.settings.link_text_color,
|
<div>
|
||||||
}}
|
<div
|
||||||
></div>
|
style={{
|
||||||
|
height: '24px',
|
||||||
|
backgroundColor: template.settings.theme_color || '#007bff',
|
||||||
|
borderRadius: '6px',
|
||||||
|
width: '80%',
|
||||||
|
marginBottom: '4px'
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Overlay для демонстрации */}
|
<div className="card-body">
|
||||||
{template.settings.group_overlay_enabled && (
|
<h6 className="card-title">{template.name}</h6>
|
||||||
<div
|
<p className="card-text small text-muted">{template.description}</p>
|
||||||
className={styles.overlayDemo}
|
|
||||||
style={{
|
{currentTemplate === template.id && (
|
||||||
backgroundColor: template.settings.group_overlay_color || '#000000',
|
<div className="mt-2">
|
||||||
opacity: template.settings.group_overlay_opacity || 0.3,
|
<span className="badge bg-primary">
|
||||||
}}
|
<i className="bi bi-check-lg me-1"></i>
|
||||||
></div>
|
Выбран
|
||||||
)}
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</button>
|
||||||
<div className="card-body p-3">
|
|
||||||
<h6
|
|
||||||
className="card-title mb-2"
|
|
||||||
style={{
|
|
||||||
fontFamily: template.settings.heading_font_family || template.settings.font_family,
|
|
||||||
color: template.settings.header_text_color
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{template.name}
|
|
||||||
</h6>
|
|
||||||
<p
|
|
||||||
className="card-text small mb-0"
|
|
||||||
style={{
|
|
||||||
fontFamily: template.settings.body_font_family || template.settings.font_family,
|
|
||||||
color: template.settings.group_description_text_color
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{template.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{currentTemplate === template.id && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<span className="badge bg-primary">
|
|
||||||
<i className="bi bi-check-lg me-1"></i>
|
|
||||||
Выбран
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -358,5 +358,79 @@ export const designTemplates: DesignTemplate[] = [
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'test-list',
|
||||||
|
name: 'Тестовый список',
|
||||||
|
description: 'Полный несворачиваемый список всех групп и ссылок',
|
||||||
|
preview: '/templates/test-list.jpg',
|
||||||
|
settings: {
|
||||||
|
theme_color: '#6366f1',
|
||||||
|
background_color: '#f8fafc',
|
||||||
|
font_family: "'Inter', sans-serif",
|
||||||
|
heading_font_family: "'Inter', sans-serif",
|
||||||
|
body_font_family: "'Inter', sans-serif",
|
||||||
|
header_text_color: '#1e293b',
|
||||||
|
group_text_color: '#334155',
|
||||||
|
link_text_color: '#475569',
|
||||||
|
group_description_text_color: '#64748b',
|
||||||
|
dashboard_layout: 'list',
|
||||||
|
group_overlay_enabled: false,
|
||||||
|
show_groups_title: true,
|
||||||
|
custom_css: `
|
||||||
|
.test-list-layout .link-group {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.test-list-layout .group-header {
|
||||||
|
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
.test-list-layout .group-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.test-list-layout .link-item {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
.test-list-layout .link-item:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-color: #6366f1;
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
.test-list-layout .link-icon {
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.test-list-layout .link-title {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
.test-list-layout .expandable-group {
|
||||||
|
/* Принудительно отключаем сворачивание */
|
||||||
|
.show-more-button { display: none !important; }
|
||||||
|
.group-links { max-height: none !important; overflow: visible !important; }
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Reference in New Issue
Block a user