278 lines
8.7 KiB
JavaScript
278 lines
8.7 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
||
import { Modal, Button, Form, Alert } from 'react-bootstrap';
|
||
import api from '../services/api';
|
||
|
||
const BookModal = ({ show, onHide, bookId, genres, onSave }) => {
|
||
const [formData, setFormData] = useState({
|
||
title: '',
|
||
author: '',
|
||
price: '',
|
||
description: '',
|
||
image: ''
|
||
});
|
||
const [selectedGenreIds, setSelectedGenreIds] = useState([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState('');
|
||
|
||
useEffect(() => {
|
||
if (bookId && show) {
|
||
loadBookData();
|
||
} else {
|
||
resetForm();
|
||
}
|
||
}, [bookId, show]);
|
||
|
||
const loadBookData = async () => {
|
||
if (!bookId) return;
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError('');
|
||
|
||
// Загружаем данные книги
|
||
const bookResponse = await api.fetchBook(bookId);
|
||
const book = bookResponse.data;
|
||
|
||
setFormData({
|
||
title: book.title || '',
|
||
author: book.author || '',
|
||
price: book.price || '',
|
||
description: book.description || '',
|
||
image: book.image || ''
|
||
});
|
||
|
||
// Загружаем жанры книги, если есть ID
|
||
if (bookId) {
|
||
try {
|
||
const genresResponse = await api.fetchBookGenres(bookId);
|
||
const bookGenres = genresResponse.data || [];
|
||
setSelectedGenreIds(bookGenres.map(gb => gb.genre.id));
|
||
} catch (genreError) {
|
||
console.error('Ошибка загрузки жанров книги:', genreError);
|
||
setSelectedGenreIds([]);
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки книги:', error);
|
||
setError('Не удалось загрузить данные книги');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const resetForm = () => {
|
||
setFormData({
|
||
title: '',
|
||
author: '',
|
||
price: '',
|
||
description: '',
|
||
image: ''
|
||
});
|
||
setSelectedGenreIds([]);
|
||
setError('');
|
||
};
|
||
|
||
const handleGenreChange = (genreId) => {
|
||
setSelectedGenreIds(prev => {
|
||
if (prev.includes(genreId)) {
|
||
return prev.filter(id => id !== genreId);
|
||
} else {
|
||
return [...prev, genreId];
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleSubmit = async (e) => {
|
||
e.preventDefault();
|
||
setError('');
|
||
|
||
try {
|
||
const bookData = {
|
||
title: formData.title,
|
||
author: formData.author,
|
||
price: Number(formData.price) || 0,
|
||
description: formData.description,
|
||
image: formData.image
|
||
};
|
||
|
||
let savedBook;
|
||
if (bookId) {
|
||
// Обновляем книгу
|
||
savedBook = await api.updateBook(bookId, bookData);
|
||
|
||
// Обновляем жанры книги
|
||
const currentGenresResponse = await api.fetchBookGenres(bookId);
|
||
const currentGenreIds = (currentGenresResponse.data || []).map(gb => gb.genre.id);
|
||
|
||
// Удаляем жанры, которые были убраны
|
||
for (const currentGenreId of currentGenreIds) {
|
||
if (!selectedGenreIds.includes(currentGenreId)) {
|
||
await api.deleteBookGenre(bookId, currentGenreId);
|
||
}
|
||
}
|
||
|
||
// Добавляем новые жанры
|
||
for (const newGenreId of selectedGenreIds) {
|
||
if (!currentGenreIds.includes(newGenreId)) {
|
||
await api.addBookGenre(bookId, {
|
||
genreId: newGenreId,
|
||
date: new Date().toISOString().split('T')[0] // Текущая дата в формате YYYY-MM-DD
|
||
});
|
||
}
|
||
}
|
||
} else {
|
||
// Создаем новую книгу
|
||
savedBook = await api.createBook(bookData);
|
||
const newBookId = savedBook.data.id;
|
||
|
||
// Добавляем жанры для новой книги
|
||
for (const genreId of selectedGenreIds) {
|
||
await api.addBookGenre(newBookId, {
|
||
genreId: genreId,
|
||
date: new Date().toISOString().split('T')[0]
|
||
});
|
||
}
|
||
}
|
||
|
||
onSave(bookData);
|
||
} catch (error) {
|
||
console.error('Ошибка сохранения книги:', error);
|
||
setError('Не удалось сохранить книгу');
|
||
}
|
||
};
|
||
|
||
const handleClose = () => {
|
||
resetForm();
|
||
onHide();
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<Modal show={show} onHide={handleClose} size="lg">
|
||
<Modal.Header closeButton>
|
||
<Modal.Title>{bookId ? 'Редактировать книгу' : 'Добавить книгу'}</Modal.Title>
|
||
</Modal.Header>
|
||
<Modal.Body className="text-center">
|
||
<div className="py-4">
|
||
<div className="spinner-border text-primary" role="status">
|
||
<span className="visually-hidden">Загрузка...</span>
|
||
</div>
|
||
<p className="mt-2">Загрузка данных...</p>
|
||
</div>
|
||
</Modal.Body>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Modal show={show} onHide={handleClose} size="lg">
|
||
<Modal.Header closeButton>
|
||
<Modal.Title>{bookId ? 'Редактировать книгу' : 'Добавить книгу'}</Modal.Title>
|
||
</Modal.Header>
|
||
<Modal.Body>
|
||
{error && (
|
||
<Alert variant="danger" className="mb-3">
|
||
{error}
|
||
</Alert>
|
||
)}
|
||
|
||
<Form onSubmit={handleSubmit}>
|
||
<Form.Group className="mb-3">
|
||
<Form.Label>Название книги *</Form.Label>
|
||
<Form.Control
|
||
type="text"
|
||
value={formData.title}
|
||
onChange={(e) => setFormData({...formData, title: e.target.value})}
|
||
required
|
||
placeholder="Введите название книги"
|
||
/>
|
||
</Form.Group>
|
||
|
||
<Form.Group className="mb-3">
|
||
<Form.Label>Автор *</Form.Label>
|
||
<Form.Control
|
||
type="text"
|
||
value={formData.author}
|
||
onChange={(e) => setFormData({...formData, author: e.target.value})}
|
||
required
|
||
placeholder="Введите автора"
|
||
/>
|
||
</Form.Group>
|
||
|
||
<Form.Group className="mb-3">
|
||
<Form.Label>Жанры</Form.Label>
|
||
<div className="border rounded p-3" style={{ maxHeight: '200px', overflowY: 'auto' }}>
|
||
{genres.length === 0 ? (
|
||
<p className="text-muted mb-0">Нет доступных жанров</p>
|
||
) : (
|
||
genres.map(genre => (
|
||
<Form.Check
|
||
key={genre.id}
|
||
type="checkbox"
|
||
id={`genre-${genre.id}`}
|
||
label={genre.name}
|
||
checked={selectedGenreIds.includes(genre.id)}
|
||
onChange={() => handleGenreChange(genre.id)}
|
||
className="mb-2"
|
||
/>
|
||
))
|
||
)}
|
||
</div>
|
||
<Form.Text className="text-muted">
|
||
Можно выбрать несколько жанров
|
||
</Form.Text>
|
||
</Form.Group>
|
||
|
||
<Form.Group className="mb-3">
|
||
<Form.Label>Цена *</Form.Label>
|
||
<Form.Control
|
||
type="number"
|
||
value={formData.price}
|
||
onChange={(e) => setFormData({...formData, price: e.target.value})}
|
||
required
|
||
min="0"
|
||
step="1"
|
||
placeholder="0"
|
||
/>
|
||
</Form.Group>
|
||
|
||
<Form.Group className="mb-3">
|
||
<Form.Label>Описание</Form.Label>
|
||
<Form.Control
|
||
as="textarea"
|
||
rows={3}
|
||
value={formData.description}
|
||
onChange={(e) => setFormData({...formData, description: e.target.value})}
|
||
placeholder="Введите описание книги"
|
||
/>
|
||
</Form.Group>
|
||
|
||
<Form.Group className="mb-3">
|
||
<Form.Label>Изображение</Form.Label>
|
||
<Form.Control
|
||
type="text"
|
||
value={formData.image}
|
||
onChange={(e) => setFormData({...formData, image: e.target.value})}
|
||
placeholder="URL изображения или путь к файлу"
|
||
/>
|
||
<Form.Text className="text-muted">
|
||
Можно указать полный URL (https://...) или относительный путь (images/book.jpg)
|
||
</Form.Text>
|
||
</Form.Group>
|
||
|
||
<Modal.Footer>
|
||
<Button variant="secondary" onClick={handleClose}>
|
||
Отмена
|
||
</Button>
|
||
<Button variant="primary" type="submit">
|
||
{bookId ? 'Обновить' : 'Добавить'}
|
||
</Button>
|
||
</Modal.Footer>
|
||
</Form>
|
||
</Modal.Body>
|
||
</Modal>
|
||
);
|
||
};
|
||
|
||
export default BookModal; |