lab6
This commit is contained in:
@@ -5,6 +5,7 @@ import CatalogPage from './pages/CatalogPage';
|
||||
import AddMoviePage from './pages/AddMoviePage';
|
||||
import EditMoviePage from './pages/EditMoviePage';
|
||||
import AboutPage from './pages/AboutPage';
|
||||
import MovieDetailsPage from './pages/MovieDetailsPage';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -48,6 +49,7 @@ function App() {
|
||||
<Route path="/catalog" element={<CatalogPage />} />
|
||||
<Route path="/add-movie" element={<AddMoviePage />} />
|
||||
<Route path="/edit-movie/:id" element={<EditMoviePage />} />
|
||||
<Route path="/movie/:id" element={<MovieDetailsPage />} />
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ function MovieCard({ movie, onDelete }) {
|
||||
)}
|
||||
</div>
|
||||
<div className="card-footer d-flex justify-content-between">
|
||||
<Link to="/about" className="btn btn-orange">
|
||||
<Link to={`/movie/${movie.id}`} className="btn btn-orange">
|
||||
<i className="bi bi-play-circle me-1"></i>Смотреть
|
||||
</Link>
|
||||
<div>
|
||||
|
||||
@@ -1,59 +1,21 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import useMovieForm from '../../hooks/useMovieForm';
|
||||
|
||||
function MovieForm({ movie, onSubmit, isEditing = false }) {
|
||||
const [title, setTitle] = useState('');
|
||||
const [director, setDirector] = useState('');
|
||||
const [genres, setGenres] = useState([]);
|
||||
const [year, setYear] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [poster, setPoster] = useState('');
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
|
||||
// Initialize form with movie data if editing
|
||||
useEffect(() => {
|
||||
if (movie) {
|
||||
setTitle(movie.title || '');
|
||||
setDirector(movie.director || '');
|
||||
setGenres(Array.isArray(movie.genres) ? movie.genres : []);
|
||||
setYear(movie.year || '');
|
||||
setDescription(movie.description || '');
|
||||
setPoster(movie.poster || '');
|
||||
if (movie.poster) {
|
||||
setPreviewVisible(true);
|
||||
}
|
||||
}
|
||||
}, [movie]);
|
||||
|
||||
const handleGenreChange = (e) => {
|
||||
const selectedGenres = Array.from(e.target.selectedOptions).map(option => option.value);
|
||||
setGenres(selectedGenres);
|
||||
};
|
||||
|
||||
const handlePosterChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
setPoster(e.target.result);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
const {
|
||||
title, setTitle,
|
||||
director, setDirector,
|
||||
genres, handleGenreChange,
|
||||
year, setYear,
|
||||
description, setDescription,
|
||||
poster, handlePosterChange,
|
||||
previewVisible,
|
||||
getFormData
|
||||
} = useMovieForm(movie);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const movieData = {
|
||||
title,
|
||||
director,
|
||||
genres,
|
||||
year,
|
||||
description,
|
||||
poster
|
||||
};
|
||||
|
||||
onSubmit(movieData);
|
||||
onSubmit(getFormData());
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
47
src/hooks/useMovie.js
Normal file
47
src/hooks/useMovie.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import MovieService from '../services/MovieService';
|
||||
|
||||
function useMovie(id) {
|
||||
const [movie, setMovie] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
setLoading(false);
|
||||
setError('Movie ID is not provided.'); // Or handle as you see fit
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchMovie = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await MovieService.getMovieById(id);
|
||||
if (!data) {
|
||||
setError('Фильм не найден');
|
||||
setMovie(null); // Ensure movie state is reset if not found
|
||||
} else {
|
||||
setMovie(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching movie:', err);
|
||||
setError('Не удалось загрузить данные фильма');
|
||||
setMovie(null); // Ensure movie state is reset on error
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMovie();
|
||||
}, [id]); // Effect runs when the id changes
|
||||
|
||||
return {
|
||||
movie,
|
||||
loading,
|
||||
error,
|
||||
setMovie // It might be useful to allow manually setting the movie, e.g., after an update
|
||||
};
|
||||
}
|
||||
|
||||
export default useMovie;
|
||||
80
src/hooks/useMovieForm.js
Normal file
80
src/hooks/useMovieForm.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function useMovieForm(initialMovieData = null) {
|
||||
const [title, setTitle] = useState('');
|
||||
const [director, setDirector] = useState('');
|
||||
const [genres, setGenres] = useState([]);
|
||||
const [year, setYear] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [poster, setPoster] = useState('');
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialMovieData) {
|
||||
setTitle(initialMovieData.title || '');
|
||||
setDirector(initialMovieData.director || '');
|
||||
setGenres(Array.isArray(initialMovieData.genres) ? initialMovieData.genres : []);
|
||||
setYear(initialMovieData.year || '');
|
||||
setDescription(initialMovieData.description || '');
|
||||
setPoster(initialMovieData.poster || '');
|
||||
if (initialMovieData.poster) {
|
||||
setPreviewVisible(true);
|
||||
} else {
|
||||
setPreviewVisible(false); // Ensure preview is hidden if no poster
|
||||
}
|
||||
} else {
|
||||
// Reset form if no initial data (e.g., for add form after an edit)
|
||||
setTitle('');
|
||||
setDirector('');
|
||||
setGenres([]);
|
||||
setYear('');
|
||||
setDescription('');
|
||||
setPoster('');
|
||||
setPreviewVisible(false);
|
||||
}
|
||||
}, [initialMovieData]);
|
||||
|
||||
const handleGenreChange = (e) => {
|
||||
const selectedGenres = Array.from(e.target.selectedOptions).map(option => option.value);
|
||||
setGenres(selectedGenres);
|
||||
};
|
||||
|
||||
const handlePosterChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
setPoster(event.target.result);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
// If no file is selected, or selection is cancelled
|
||||
// setPoster(''); // Optionally clear poster if no file is chosen
|
||||
// setPreviewVisible(false); // Optionally hide preview
|
||||
}
|
||||
};
|
||||
|
||||
const getFormData = () => ({
|
||||
title,
|
||||
director,
|
||||
genres,
|
||||
year,
|
||||
description,
|
||||
poster
|
||||
});
|
||||
|
||||
// Exposed state and handlers
|
||||
return {
|
||||
title, setTitle,
|
||||
director, setDirector,
|
||||
genres, setGenres, handleGenreChange, // Expose specific handler for genres
|
||||
year, setYear,
|
||||
description, setDescription,
|
||||
poster, setPoster, handlePosterChange, // Expose specific handler for poster
|
||||
previewVisible,
|
||||
getFormData // Function to get all form data for submission
|
||||
};
|
||||
}
|
||||
|
||||
export default useMovieForm;
|
||||
71
src/hooks/useMovies.js
Normal file
71
src/hooks/useMovies.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import MovieService from '../services/MovieService';
|
||||
|
||||
function useMovies() {
|
||||
const [movies, setMovies] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedGenre, setSelectedGenre] = useState('');
|
||||
const [genres, setGenres] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMoviesAndGenres = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const data = await MovieService.getMovies();
|
||||
setMovies(data);
|
||||
|
||||
const allGenres = data.flatMap(movie => movie.genres || []);
|
||||
const uniqueGenres = [...new Set(allGenres)];
|
||||
setGenres(uniqueGenres);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error fetching movies:', err);
|
||||
setError('Не удалось загрузить фильмы. Пожалуйста, попробуйте позже.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMoviesAndGenres();
|
||||
}, []);
|
||||
|
||||
const handleDeleteMovie = async (id) => {
|
||||
try {
|
||||
await MovieService.deleteMovie(id);
|
||||
setMovies(prevMovies => prevMovies.filter(movie => movie.id !== id));
|
||||
} catch (err) {
|
||||
console.error('Error deleting movie:', err);
|
||||
alert('Произошла ошибка при удалении фильма.');
|
||||
// Optionally, re-throw or set an error state for the component to handle
|
||||
}
|
||||
};
|
||||
|
||||
const filteredMovies = movies.filter(movie => {
|
||||
const matchesSearch = searchTerm ?
|
||||
(movie.title?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
movie.director?.toLowerCase().includes(searchTerm.toLowerCase())) : true;
|
||||
|
||||
const matchesGenre = selectedGenre === '' ||
|
||||
(Array.isArray(movie.genres) && movie.genres.includes(selectedGenre));
|
||||
|
||||
return matchesSearch && matchesGenre;
|
||||
});
|
||||
|
||||
return {
|
||||
movies: filteredMovies, // Return filtered movies
|
||||
loading,
|
||||
error,
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
selectedGenre,
|
||||
setSelectedGenre,
|
||||
genres, // Original list of unique genres for the dropdown
|
||||
handleDeleteMovie,
|
||||
allMovies: movies // Raw list of movies if needed elsewhere, though typically not exposed directly
|
||||
};
|
||||
}
|
||||
|
||||
export default useMovies;
|
||||
@@ -1,60 +1,23 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import MovieList from '../components/Movie/MovieList';
|
||||
import MovieService from '../services/MovieService';
|
||||
// import MovieService from '../services/MovieService'; // No longer needed here
|
||||
import useMovies from '../hooks/useMovies'; // Import the custom hook
|
||||
|
||||
function CatalogPage() {
|
||||
const [movies, setMovies] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// For filtering
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedGenre, setSelectedGenre] = useState('');
|
||||
const [genres, setGenres] = useState([]);
|
||||
const {
|
||||
movies, // This is now filteredMovies from the hook
|
||||
loading,
|
||||
error,
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
selectedGenre,
|
||||
setSelectedGenre,
|
||||
genres, // This is the unique genres list from the hook
|
||||
handleDeleteMovie
|
||||
} = useMovies();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMovies = async () => {
|
||||
try {
|
||||
const data = await MovieService.getMovies();
|
||||
setMovies(data);
|
||||
|
||||
// Extract unique genres for filter
|
||||
const allGenres = data.flatMap(movie => movie.genres || []);
|
||||
const uniqueGenres = [...new Set(allGenres)];
|
||||
setGenres(uniqueGenres);
|
||||
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error fetching movies:', error);
|
||||
setError('Не удалось загрузить фильмы. Пожалуйста, попробуйте позже.');
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMovies();
|
||||
}, []);
|
||||
|
||||
const handleDeleteMovie = async (id) => {
|
||||
try {
|
||||
await MovieService.deleteMovie(id);
|
||||
setMovies(movies.filter(movie => movie.id !== id));
|
||||
} catch (error) {
|
||||
console.error('Error deleting movie:', error);
|
||||
alert('Произошла ошибка при удалении фильма.');
|
||||
}
|
||||
};
|
||||
|
||||
// Filter movies based on search term and selected genre
|
||||
const filteredMovies = movies.filter(movie => {
|
||||
const matchesSearch = searchTerm ?
|
||||
(movie.title?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
movie.director?.toLowerCase().includes(searchTerm.toLowerCase())) : true;
|
||||
|
||||
const matchesGenre = selectedGenre === '' ||
|
||||
(Array.isArray(movie.genres) && movie.genres.includes(selectedGenre));
|
||||
|
||||
return matchesSearch && matchesGenre;
|
||||
});
|
||||
// useEffect and handleDeleteMovie logic is now in useMovies hook
|
||||
// Filtered movies logic is also in useMovies hook
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center py-5"><div className="spinner-border" role="status"></div></div>;
|
||||
@@ -90,7 +53,8 @@ function CatalogPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MovieList movies={filteredMovies} onDeleteMovie={handleDeleteMovie} />
|
||||
{/* Pass the movies (which are already filtered) and onDeleteMovie from the hook */}
|
||||
<MovieList movies={movies} onDeleteMovie={handleDeleteMovie} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,42 +1,21 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import MovieForm from '../components/Movie/MovieForm';
|
||||
import MovieService from '../services/MovieService';
|
||||
import useMovie from '../hooks/useMovie';
|
||||
|
||||
function EditMoviePage() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [movie, setMovie] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMovie = async () => {
|
||||
try {
|
||||
const data = await MovieService.getMovieById(id);
|
||||
if (!data) {
|
||||
setError('Фильм не найден');
|
||||
} else {
|
||||
setMovie(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching movie:', error);
|
||||
setError('Не удалось загрузить данные фильма');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMovie();
|
||||
}, [id]);
|
||||
const { movie, loading, error, setMovie } = useMovie(id);
|
||||
|
||||
const handleUpdateMovie = async (movieData) => {
|
||||
try {
|
||||
await MovieService.updateMovie(id, movieData);
|
||||
const updatedMovie = await MovieService.updateMovie(id, movieData);
|
||||
alert('Фильм успешно обновлен!');
|
||||
navigate('/catalog');
|
||||
} catch (error) {
|
||||
console.error('Error updating movie:', error);
|
||||
} catch (err) {
|
||||
console.error('Error updating movie:', err);
|
||||
alert('Произошла ошибка при обновлении фильма.');
|
||||
}
|
||||
};
|
||||
@@ -49,6 +28,10 @@ function EditMoviePage() {
|
||||
return <div className="alert alert-danger">{error}</div>;
|
||||
}
|
||||
|
||||
if (!movie) {
|
||||
return <div className="alert alert-warning">Фильм для редактирования не найден.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="mb-4">Редактировать фильм</h2>
|
||||
|
||||
61
src/pages/MovieDetailsPage.jsx
Normal file
61
src/pages/MovieDetailsPage.jsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import useMovie from '../hooks/useMovie';
|
||||
|
||||
function MovieDetailsPage() {
|
||||
const { id } = useParams();
|
||||
const { movie, loading, error } = useMovie(id);
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center py-5"><div className="spinner-border" role="status"></div></div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="alert alert-danger">{error}</div>;
|
||||
}
|
||||
|
||||
if (!movie) {
|
||||
return <div className="alert alert-warning">Фильм не найден.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<img src={movie.poster} className="img-fluid rounded shadow-sm" alt={`${movie.title} Poster`} />
|
||||
</div>
|
||||
<div className="col-md-8">
|
||||
<h1 className="mb-3 text-orange">{movie.title}</h1>
|
||||
<p className="lead">
|
||||
<i className="bi bi-person-video3 text-secondary me-2"></i>
|
||||
<strong>Режиссер:</strong> {movie.director}
|
||||
</p>
|
||||
<p>
|
||||
<i className="bi bi-calendar3 text-secondary me-2"></i>
|
||||
<strong>Год:</strong> {movie.year}
|
||||
</p>
|
||||
<p>
|
||||
<i className="bi bi-tags text-secondary me-2"></i>
|
||||
<strong>Жанры:</strong> {Array.isArray(movie.genres) ? movie.genres.join(', ') : movie.genres}
|
||||
</p>
|
||||
<hr />
|
||||
<h5 className="mt-4 mb-3">Описание:</h5>
|
||||
<p style={{ textAlign: 'justify' }}>{movie.description}</p>
|
||||
<hr />
|
||||
<div className="mt-4">
|
||||
<Link to={`/edit-movie/${movie.id}`} className="btn btn-warning me-2">
|
||||
<i className="bi bi-pencil me-1"></i> Редактировать
|
||||
</Link>
|
||||
<Link to="/catalog" className="btn btn-outline-secondary">
|
||||
<i className="bi bi-arrow-left-circle me-1"></i> Назад к каталогу
|
||||
</Link>
|
||||
{/* Placeholder for a play button or video embed area */}
|
||||
{/* <button className=\"btn btn-lg btn-success mt-3 w-100\">Смотреть фильм</button> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieDetailsPage;
|
||||
BIN
Отчет.docx
BIN
Отчет.docx
Binary file not shown.
Reference in New Issue
Block a user