print_add
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import 'quill/dist/quill.snow.css';
|
||||
import '../App.css';
|
||||
import DOMPurify from 'dompurify';
|
||||
import SlidesManager from './SlidesManager';
|
||||
|
||||
function Editor() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
@@ -15,7 +16,6 @@ function Editor() {
|
||||
const [editContent, setEditContent] = useState('');
|
||||
const [editPreviewContent, setEditPreviewContent] = useState('');
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||
//const [csrfToken, setCsrfToken] = useState('');
|
||||
const [notify, setNotify] = useState({ message: '', type: '', show: false });
|
||||
|
||||
const editorRef = useRef(null);
|
||||
@@ -29,17 +29,6 @@ function Editor() {
|
||||
setNotify({ message: '', type: '', show: false });
|
||||
}, 2200);
|
||||
}, []);
|
||||
|
||||
|
||||
// Fetch CSRF token
|
||||
//useEffect(() => {
|
||||
// fetch('/moders/csrf-token')
|
||||
// .then(res => res.json())
|
||||
// .then(data => {
|
||||
// setCsrfToken(data.csrfToken);
|
||||
//})
|
||||
//.catch(() => {});
|
||||
//}, []);
|
||||
|
||||
// Check session on mount
|
||||
useEffect(() => {
|
||||
@@ -55,9 +44,9 @@ function Editor() {
|
||||
.catch(() => setIsLoggedIn(false));
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
return fetch(url, options);
|
||||
}, []);
|
||||
const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
return fetch(url, options);
|
||||
}, []);
|
||||
|
||||
const loadBlocks = useCallback(async (tab) => {
|
||||
try {
|
||||
@@ -105,7 +94,7 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
|
||||
const handleTabChange = (tab) => {
|
||||
setActiveTab(tab);
|
||||
if (isLoggedIn) {
|
||||
if (isLoggedIn && tab !== 'slides') {
|
||||
loadBlocks(tab);
|
||||
}
|
||||
};
|
||||
@@ -138,41 +127,35 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
};
|
||||
|
||||
const handleSaveBlock = async (e) => {
|
||||
e.preventDefault();
|
||||
const content = DOMPurify.sanitize(editContent.trim());
|
||||
if (!content || content === '<p><br></p>') return;
|
||||
e.preventDefault();
|
||||
const content = DOMPurify.sanitize(editContent.trim());
|
||||
if (!content || content === '<p><br></p>') return;
|
||||
|
||||
const url = editingBlockId
|
||||
? `/moders/content/${activeTab}/${editingBlockId}`
|
||||
: `/moders/content/${activeTab}`;
|
||||
const url = editingBlockId
|
||||
? `/moders/content/${activeTab}/${editingBlockId}`
|
||||
: `/moders/content/${activeTab}`;
|
||||
|
||||
const method = editingBlockId ? 'PUT' : 'POST';
|
||||
const method = editingBlockId ? 'PUT' : 'POST';
|
||||
|
||||
console.log('➡️ Отправка:', method, url, { type: 'text', content });
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'text', content })
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'text', content })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
console.log('⬅️ Ответ сервера:', response.status, data);
|
||||
|
||||
if (response.ok) {
|
||||
showNotify(editingBlockId ? 'Блок обновлён' : 'Блок добавлен', 'success');
|
||||
setShowEditModal(false);
|
||||
loadBlocks(activeTab);
|
||||
} else {
|
||||
showNotify(`Ошибка: ${data.message || response.status}`, 'error');
|
||||
if (response.ok) {
|
||||
showNotify(editingBlockId ? 'Блок обновлён' : 'Блок добавлен', 'success');
|
||||
setShowEditModal(false);
|
||||
loadBlocks(activeTab);
|
||||
} else {
|
||||
const data = await response.json();
|
||||
showNotify(`Ошибка: ${data.message || response.status}`, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotify('Ошибка сохранения', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('❌ Ошибка при сохранении:', e);
|
||||
showNotify('Ошибка сохранения', 'error');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleTryCloseEditModal = () => {
|
||||
if (editContent !== originalContentRef.current) {
|
||||
@@ -194,7 +177,6 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
// Initialize Quill when modal opens
|
||||
useEffect(() => {
|
||||
if (showEditModal && editorRef.current && !quillRef.current) {
|
||||
// Dynamically import Quill
|
||||
import('quill').then((QuillModule) => {
|
||||
const Quill = QuillModule.default || QuillModule;
|
||||
if (editorRef.current) {
|
||||
@@ -225,51 +207,68 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup Quill when modal closes
|
||||
if (!showEditModal && quillRef.current) {
|
||||
quillRef.current = null;
|
||||
}
|
||||
}, [showEditModal, editContent]);
|
||||
|
||||
// Если не авторизован — показываем форму входа
|
||||
if (!isLoggedIn) {
|
||||
return (
|
||||
<div className="container">
|
||||
<h1>Панель модератора</h1>
|
||||
<div id="login-section">
|
||||
<h2>Вход для модератора</h2>
|
||||
<form id="moder-login-form" onSubmit={handleLogin}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="username">Логин:</label>
|
||||
<input type="text" id="username" name="username" required value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Пароль:</label>
|
||||
<input type="password" id="password" name="password" required value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
<button type="submit">Войти</button>
|
||||
{loginError && <p id="login-error" style={{ color: 'red' }}>{loginError}</p>}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Авторизован — показываем редактор
|
||||
return (
|
||||
<>
|
||||
<div id="notify" className={notify.show ? `show ${notify.type}` : ''}>{notify.message}</div>
|
||||
<div className="container">
|
||||
<h1>Панель модератора</h1>
|
||||
{!isLoggedIn ? (
|
||||
<div id="login-section">
|
||||
<h2>Вход для модератора</h2>
|
||||
<form id="moder-login-form" onSubmit={handleLogin}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="username">Логин:</label>
|
||||
<input type="text" id="username" name="username" required value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Пароль:</label>
|
||||
<input type="password" id="password" name="password" required value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
<button type="submit">Войти</button>
|
||||
{loginError && <p id="login-error" style={{ color: 'red' }}>{loginError}</p>}
|
||||
</form>
|
||||
</div>
|
||||
<button id="logout-btn" style={{ float: 'right' }} onClick={handleLogout}>Выйти из системы</button>
|
||||
<h2>Редактирование вкладок</h2>
|
||||
|
||||
<div className="tab-controls">
|
||||
<button className={`tab-btn ${activeTab === 'info' ? 'active' : ''}`} onClick={() => handleTabChange('info')} data-tab="info">
|
||||
Общая информация
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'events' ? 'active' : ''}`} onClick={() => handleTabChange('events')} data-tab="events">
|
||||
📅 Мероприятия
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'help' ? 'active' : ''}`} onClick={() => handleTabChange('help')} data-tab="help">
|
||||
💰 Материальная помощь
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'housing' ? 'active' : ''}`} onClick={() => handleTabChange('housing')} data-tab="housing">
|
||||
🏠 Жилищные условия
|
||||
</button>
|
||||
{/* ✅ НОВАЯ ВКЛАДКА СЛАЙДЫ */}
|
||||
<button className={`tab-btn ${activeTab === 'slides' ? 'active' : ''}`} onClick={() => setActiveTab('slides')} data-tab="slides">
|
||||
📸 Слайды
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Рендеринг контента вкладок */}
|
||||
{activeTab === 'slides' ? (
|
||||
<SlidesManager />
|
||||
) : (
|
||||
<div id="editor-section">
|
||||
<button id="logout-btn" style={{ float: 'right' }} onClick={handleLogout}>Выйти из системы</button>
|
||||
<h2>Редактирование вкладок</h2>
|
||||
<div className="tab-controls">
|
||||
<button className={`tab-btn ${activeTab === 'info' ? 'active' : ''}`} onClick={() => handleTabChange('info')} data-tab="info">
|
||||
Общая информация
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'events' ? 'active' : ''}`} onClick={() => handleTabChange('events')} data-tab="events">
|
||||
📅 Мероприятия
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'help' ? 'active' : ''}`} onClick={() => handleTabChange('help')} data-tab="help">
|
||||
💰 Материальная помощь
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'housing' ? 'active' : ''}`} onClick={() => handleTabChange('housing')} data-tab="housing">
|
||||
🏠 Жилищные условия
|
||||
</button>
|
||||
</div>
|
||||
<>
|
||||
<div style={{ margin: '18px 0' }}>
|
||||
<button id="add-block-btn" onClick={handleAddBlock}>Добавить блок</button>
|
||||
</div>
|
||||
@@ -283,9 +282,10 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Модалка редактирования блоков */}
|
||||
{showEditModal && (
|
||||
<div id="edit-modal" className="modal" style={{ display: 'block' }} onClick={(e) => {
|
||||
if (e.target === e.currentTarget) handleTryCloseEditModal();
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
// src/components/SlidesManager.jsx
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
function SlidesManager() {
|
||||
const [slides, setSlides] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [editingSlide, setEditingSlide] = useState(null);
|
||||
const [formData, setFormData] = useState({
|
||||
image_url: '',
|
||||
title: '',
|
||||
description: '',
|
||||
link: '',
|
||||
order: 0,
|
||||
is_active: 1
|
||||
});
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [notify, setNotify] = useState({ message: '', type: '', show: false });
|
||||
|
||||
const showNotify = useCallback((message, type = 'success') => {
|
||||
setNotify({ message, type, show: true });
|
||||
setTimeout(() => {
|
||||
setNotify({ message: '', type: '', show: false });
|
||||
}, 2500);
|
||||
}, []);
|
||||
|
||||
// Загрузка слайдов
|
||||
const loadSlides = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch('/moders/content/slides/all', {
|
||||
credentials: 'include'
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setSlides(Array.isArray(data) ? data : []);
|
||||
} else {
|
||||
showNotify('Ошибка загрузки слайдов', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotify('Ошибка загрузки слайдов', 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [showNotify]);
|
||||
|
||||
useEffect(() => {
|
||||
loadSlides();
|
||||
}, [loadSlides]);
|
||||
|
||||
// Открыть модалку для добавления
|
||||
const handleAddSlide = () => {
|
||||
setEditingSlide(null);
|
||||
setFormData({
|
||||
image_url: '',
|
||||
title: '',
|
||||
description: '',
|
||||
link: '',
|
||||
order: slides.length,
|
||||
is_active: 1
|
||||
});
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
// Открыть модалку для редактирования
|
||||
const handleEditSlide = (slide) => {
|
||||
setEditingSlide(slide);
|
||||
setFormData({
|
||||
image_url: slide.image_url || '',
|
||||
title: slide.title || '',
|
||||
description: slide.description || '',
|
||||
link: slide.link || '',
|
||||
order: slide.order || 0,
|
||||
is_active: slide.is_active !== undefined ? slide.is_active : 1
|
||||
});
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
// Сохранить слайд
|
||||
// В функции handleSaveSlide
|
||||
const handleSaveSlide = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// ✅ ЛОГИ ДЛЯ ОТЛАДКИ
|
||||
console.log('========================================');
|
||||
console.log('📤 ОТПРАВКА ДАННЫХ НА СЕРВЕР');
|
||||
console.log('📤 editingSlide:', editingSlide);
|
||||
console.log('📤 formData:', JSON.stringify(formData, null, 2));
|
||||
console.log('========================================');
|
||||
|
||||
if (!formData.image_url) {
|
||||
showNotify('URL изображения обязателен', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = editingSlide
|
||||
? `/moders/content/slides/${editingSlide.id}`
|
||||
: '/moders/content/slides';
|
||||
|
||||
const method = editingSlide ? 'PUT' : 'POST';
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
console.log('📥 ОТВЕТ СЕРВЕРА:', res.status, data);
|
||||
|
||||
if (res.ok) {
|
||||
showNotify(editingSlide ? 'Слайд обновлён' : 'Слайд добавлен', 'success');
|
||||
setShowEditModal(false);
|
||||
loadSlides();
|
||||
} else {
|
||||
showNotify(data.message || `Ошибка: ${res.status}`, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('❌ Ошибка:', e);
|
||||
showNotify('Ошибка сохранения', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Удалить слайд
|
||||
const handleDeleteSlide = async (id) => {
|
||||
if (!window.confirm('Удалить этот слайд?')) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/moders/content/slides/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include'
|
||||
});
|
||||
if (res.ok) {
|
||||
showNotify('Слайд удалён', 'success');
|
||||
loadSlides();
|
||||
} else {
|
||||
showNotify('Ошибка удаления', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotify('Ошибка удаления', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Загрузка изображения
|
||||
const handleUploadImage = async (file) => {
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
setUploading(true);
|
||||
try {
|
||||
const res = await fetch('/moders/content/slides/upload', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: formData
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setFormData(prev => ({ ...prev, image_url: data.path }));
|
||||
showNotify('Изображение загружено', 'success');
|
||||
} else {
|
||||
showNotify('Ошибка загрузки изображения', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotify('Ошибка загрузки изображения', 'error');
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="slides-manager">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||
<h2>📸 Слайды карусели</h2>
|
||||
<button id="add-slide-btn" onClick={handleAddSlide}>+ Добавить слайд</button>
|
||||
</div>
|
||||
|
||||
{/* Уведомления */}
|
||||
{notify.show && (
|
||||
<div className={`notify ${notify.type}`} style={{
|
||||
padding: '10px 16px',
|
||||
marginBottom: '15px',
|
||||
borderRadius: '8px',
|
||||
background: notify.type === 'success' ? '#d4edda' : '#f8d7da',
|
||||
color: notify.type === 'success' ? '#155724' : '#721c24',
|
||||
border: `1px solid ${notify.type === 'success' ? '#c3e6cb' : '#f5c6cb'}`
|
||||
}}>
|
||||
{notify.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Список слайдов */}
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>Загрузка...</div>
|
||||
) : slides.length === 0 ? (
|
||||
<p style={{ color: '#999', textAlign: 'center', padding: '40px' }}>Нет слайдов. Добавьте первый!</p>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '20px' }}>
|
||||
{slides.map(slide => (
|
||||
<div key={slide.id} style={{
|
||||
background: '#fff',
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
||||
border: `2px solid ${slide.is_active ? '#28a745' : '#dc3545'}`,
|
||||
position: 'relative'
|
||||
}}>
|
||||
<img
|
||||
src={slide.image_url}
|
||||
alt={slide.title || 'Слайд'}
|
||||
style={{ width: '100%', height: '150px', objectFit: 'cover' }}
|
||||
onError={(e) => {
|
||||
e.target.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="100%25" height="100%25"%3E%3Crect width="100%25" height="100%25" fill="%23ddd"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" dy=".3em" fill="%23999" font-size="14"%3E%D0%9D%D0%B5%D1%82%20%D0%B8%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F%3C/text%3E%3C/svg%3E';
|
||||
}}
|
||||
/>
|
||||
<div style={{ padding: '12px 16px' }}>
|
||||
<h4 style={{ margin: '0 0 4px 0', fontSize: '16px' }}>{slide.title || 'Без названия'}</h4>
|
||||
<p style={{ margin: '0 0 8px 0', fontSize: '13px', color: '#666', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
|
||||
{slide.description || ''}
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: '8px', fontSize: '12px', color: '#999', marginBottom: '10px' }}>
|
||||
<span>Порядок: {slide.order}</span>
|
||||
<span>•</span>
|
||||
<span style={{ color: slide.is_active ? '#28a745' : '#dc3545' }}>
|
||||
{slide.is_active ? 'Активен' : 'Неактивен'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button className="btn-small" onClick={() => handleEditSlide(slide)}>✏️</button>
|
||||
<button className="btn-small" onClick={() => handleDeleteSlide(slide.id)}>🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Модалка редактирования */}
|
||||
{showEditModal && (
|
||||
<div className="modal" style={{
|
||||
display: 'block',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'rgba(0,0,0,0.5)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
zIndex: 2000,
|
||||
padding: '20px',
|
||||
overflowY: 'auto'
|
||||
}} onClick={(e) => {
|
||||
if (e.target === e.currentTarget) setShowEditModal(false);
|
||||
}}>
|
||||
<div className="modal-content" style={{
|
||||
maxWidth: '700px',
|
||||
margin: '40px auto',
|
||||
background: '#fff',
|
||||
borderRadius: '16px',
|
||||
padding: '30px',
|
||||
boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<button className="close" style={{
|
||||
position: 'absolute',
|
||||
top: '12px',
|
||||
right: '20px',
|
||||
fontSize: '28px',
|
||||
cursor: 'pointer',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: '#999'
|
||||
}} onClick={() => setShowEditModal(false)}>×</button>
|
||||
|
||||
<h3 style={{ marginBottom: '20px' }}>
|
||||
{editingSlide ? 'Редактировать слайд' : 'Добавить слайд'}
|
||||
</h3>
|
||||
|
||||
<form onSubmit={handleSaveSlide}>
|
||||
<div className="form-group" style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 500 }}>
|
||||
Изображение URL или загрузить:
|
||||
</label>
|
||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.image_url}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, image_url: e.target.value }))}
|
||||
placeholder="https://example.com/image.jpg"
|
||||
style={{ flex: 1, padding: '8px 12px', borderRadius: '8px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
<label style={{
|
||||
padding: '8px 16px',
|
||||
background: '#6c757d',
|
||||
color: '#fff',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '13px'
|
||||
}}>
|
||||
{uploading ? 'Загрузка...' : '📤 Загрузить'}
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style={{ display: 'none' }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) handleUploadImage(file);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 500 }}>Заголовок:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
|
||||
placeholder="Заголовок слайда"
|
||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '8px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 500 }}>Описание:</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
||||
placeholder="Описание слайда"
|
||||
rows="3"
|
||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '8px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ marginBottom: '15px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 500 }}>Ссылка (опционально):</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.link}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, link: e.target.value }))}
|
||||
placeholder="https://example.com"
|
||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '8px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: '20px', marginBottom: '15px' }}>
|
||||
<div className="form-group" style={{ flex: 1 }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 500 }}>Порядок:</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.order}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, order: parseInt(e.target.value) || 0 }))}
|
||||
min="0"
|
||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '8px', border: '1px solid #ddd' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ flex: 1 }}>
|
||||
<label style={{ display: 'block', marginBottom: '5px', fontWeight: 500 }}>Статус:</label>
|
||||
<select
|
||||
value={formData.is_active}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, is_active: parseInt(e.target.value) }))}
|
||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '8px', border: '1px solid #ddd' }}
|
||||
>
|
||||
<option value="1">Активен</option>
|
||||
<option value="0">Неактивен</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" style={{
|
||||
padding: '10px 30px',
|
||||
background: '#6c5ce7',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '10px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '15px',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{editingSlide ? 'Обновить' : 'Добавить'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SlidesManager;
|
||||
Reference in New Issue
Block a user