import { useState, useEffect } from 'react'; import { FaFire, FaEdit, FaTrashAlt } from 'react-icons/fa'; // Changed FaStar to FaFire import RatingChart from './RatingChart'; import FlameRatingInput from './FlameRatingInput'; // Import the new component import ReactQuill from 'react-quill'; // Import ReactQuill import 'react-quill/dist/quill.snow.css'; // Import Quill styles (snow theme) // Default characteristics if none are provided (fallback) const defaultCharacteristics = { overall: 'Общая оценка' // Ensure a default 'overall' characteristic }; // Mapping for watched/completed status values const watchedStatusLabels = { not_watched: 'Не просмотрено', watched: 'Просмотрено', }; const completedStatusLabels = { not_completed: 'Не пройдено', completed: 'Пройдено', }; // ReviewForm now expects characteristics prop to be the media's characteristics { [key]: label } // Also expects mediaType, progressType, seasons, and selectedSeasonId function ReviewForm({ mediaId, seasonId, mediaType, progressType, onSubmit, onEdit, onDelete, characteristics = defaultCharacteristics, existingReview, seasons = [], selectedSeasonId }) { // Initial state for ratings, now storing just the number { [key]: number } // Initialize with a default value (e.g., 5) for each characteristic provided const initialRatings = Object.keys(characteristics).reduce((acc, key) => { acc[key] = 5; // Default rating of 5 return acc; }, {}); const [ratings, setRatings] = useState(initialRatings); // Use empty string for Quill content state initially const [content, setContent] = useState(''); const [hasSpoilers, setHasSpoilers] = useState(false); // Use 'progress' state instead of 'status' const [progress, setProgress] = useState(''); // State for progress (text field) const [isSubmitting, setIsSubmitting] = useState(false); const [isEditing, setIsEditing] = useState(false); // New state for selected season in the form (only relevant if media supports seasons) // Initialize with the selectedSeasonId passed from the parent const [formSeasonId, setFormSeasonId] = useState(selectedSeasonId); // Determine the correct progress options/label based *only* on progressType const getProgressOptions = () => { if (progressType === 'watched') { return watchedStatusLabels; } else if (progressType === 'completed') { return completedStatusLabels; } // If progressType is 'hours' or unknown, use text input return null; }; const progressOptions = getProgressOptions(); const isProgressSelect = progressOptions !== null; // Determine if we should show a select/segmented control // Determine if the media type supports seasons const supportsSeasons = mediaType === 'tv' || mediaType === 'anime'; // Reset ratings, content, progress, and formSeasonId when characteristics, existingReview, progressType, or selectedSeasonId change useEffect(() => { console.log('ReviewForm useEffect: existingReview changed', existingReview); // LOG console.log('ReviewForm useEffect: selectedSeasonId changed', selectedSeasonId); // LOG // Ensure characteristics is an object before processing const validCharacteristics = characteristics && typeof characteristics === 'object' ? characteristics : defaultCharacteristics; if (existingReview) { // If editing, pre-fill form with existing review data // Expect existingReview.ratings to be { [key]: number } const populatedRatings = Object.keys(validCharacteristics).reduce((acc, key) => { // Use existing rating if it's a number, otherwise default to 5 acc[key] = typeof existingReview.ratings?.[key] === 'number' ? existingReview.ratings[key] : 5; return acc; }, {}); setRatings(populatedRatings); // Set content from existing review (assuming it's HTML) setContent(existingReview.content || ''); setHasSpoilers(existingReview.has_spoilers ?? false); // Initialize progress from existing review setProgress(existingReview.progress || ''); // Initialize formSeasonId from existing review's season_id setFormSeasonId(existingReview.season_id || null); // Use null for overall review console.log('ReviewForm useEffect: Setting isEditing to false (existing review)'); // LOG setIsEditing(false); // Start in view mode } else { // If creating, reset form const newInitialRatings = Object.keys(validCharacteristics).reduce((acc, key) => { acc[key] = 5; // Default rating of 5 return acc; }, {}); setRatings(newInitialRatings); setContent(''); // Reset content to empty string for Quill setHasSpoilers(false); // Reset progress based on the type of input expected if (isProgressSelect) { // Set default for select based on options (e.g., 'completed' or 'not_watched') // Default to the first option key, which should be the 'not_' status setProgress(Object.keys(progressOptions)[0] || ''); } else { setProgress(''); // Default to empty string for text input (hours) } // Reset formSeasonId to the currently selected season on the page setFormSeasonId(selectedSeasonId); console.log('ReviewForm useEffect: Resetting form, setting isEditing to false (no existing review)'); // LOG setIsEditing(false); } }, [characteristics, existingReview, progressType, isProgressSelect, selectedSeasonId, seasons]); // Depend on selectedSeasonId and seasons too // Add a log to see when isEditing state changes useEffect(() => { console.log('ReviewForm: isEditing state changed to', isEditing); // LOG }, [isEditing]); const handleRatingChange = (category, value) => { const newRatings = { ...ratings, [category]: value }; // Рассчитываем overall_rating как среднее всех оценок const validRatings = Object.values(newRatings).filter(rating => typeof rating === 'number'); const overallRating = validRatings.length > 0 ? validRatings.reduce((sum, rating) => sum + rating, 0) / validRatings.length : 0; setRatings({ ...newRatings, overall: overallRating }); }; const handleProgressChange = (value) => { setProgress(value); }; const handleFormSeasonChange = (e) => { // Convert the value to null if it's the "Общее" option const value = e.target.value === '' ? null : e.target.value; setFormSeasonId(value); }; const handleSubmit = async (e) => { e.preventDefault(); setIsSubmitting(true); try { // Рассчитываем overall_rating перед отправкой const validRatings = Object.entries(ratings) .filter(([key, value]) => key !== 'overall' && typeof value === 'number'); const overallRating = validRatings.length > 0 ? validRatings.reduce((sum, [_, rating]) => sum + rating, 0) / validRatings.length : 0; const reviewData = { media_id: mediaId, season_id: formSeasonId, media_type: mediaType, content, ratings: { ...ratings, overall: overallRating }, has_spoilers: hasSpoilers, progress: progress, progress_type: progressType }; if (existingReview && isEditing) { await onEdit(existingReview.id, reviewData); setIsEditing(false); } else { await onSubmit(reviewData); } } catch (error) { console.error('Error submitting review:', error); } finally { setIsSubmitting(false); } }; const handleDelete = async () => { if (window.confirm('Вы уверены, что хотите удалить эту рецензию?')) { setIsSubmitting(true); try { await onDelete(existingReview.id, mediaId); // deleteReview only needs reviewId, mediaId is optional // Form reset is handled by the useEffect when existingReview becomes null } catch (error) { console.error('Error deleting review:', error); // Optionally set an error state } finally { setIsSubmitting(false); } } }; // Determine if the form is valid for submission // Content must not be empty, ALL characteristics must have a valid rating (1-10), and progress must be filled/selected const isFormValid = content.trim() !== '' && content !== '


' && // Check content validity Object.keys(characteristics).length > 0 && // Ensure characteristics are loaded Object.keys(characteristics).every(key => typeof ratings[key] === 'number' && ratings[key] >= 1 && ratings[key] <= 10 ) && (isProgressSelect ? Object.keys(progressOptions).includes(progress) : progress.trim() !== '') && // Check progress validity based on input type (supportsSeasons ? (formSeasonId === null || seasons.some(s => s.id === formSeasonId)) : true); // Check season selection validity // If an existing review is present and not in editing mode, show review details instead of the form if (existingReview && !isEditing) { return (

Вы уже написали рецензию на это произведение.

{/* Fixed delete button text color */}
); } // Quill modules - define toolbar options const modules = { toolbar: [ [{ 'header': [1, 2, false] }], ['bold', 'italic', 'underline', 'strike', 'blockquote'], [{ 'list': 'ordered' }, { 'list': 'bullet' }], ['link'], ['clean'] ], }; const formats = [ 'header', 'bold', 'italic', 'underline', 'strike', 'blockquote', 'list', 'bullet', 'link' ]; return (

{existingReview ? 'Редактировать рецензию' : 'Написать рецензию'}

{/* Add a log to see if this form section is being rendered */} {existingReview && isEditing && console.log('ReviewForm: Rendering Edit Form')} {!existingReview && console.log('ReviewForm: Rendering Create Form')}
{/* Season Selection (if media supports seasons) */} {supportsSeasons && seasons.length > 0 && (
)} {/* Progress Input - Conditional based on progressType */}
{isProgressSelect ? ( // Segmented control for watched/completed status
{Object.entries(progressOptions).map(([key, label]) => ( ))}
) : ( // Text input for hours (for games with progressType 'hours') or fallback handleProgressChange(e.target.value)} className="w-full p-3 bg-campfire-dark border border-campfire-ash/30 rounded-md text-campfire-light focus:ring-2 focus:ring-campfire-amber focus:border-transparent transition-colors" required /> )} {/* Add a hidden required input to satisfy HTML5 validation */} {/* This hidden input is a fallback; actual validation is in isFormValid */} {/* Removed the hidden input as isFormValid handles validation */}
{/* Rating Inputs - Using FlameRatingInput */}
{/* Increased spacing */} {/* Iterate over characteristics provided by the media */} {Object.entries(characteristics).map(([key, label]) => (
{/* Display value - Changed FaStar to FaFire */} {/* Made value larger/bolder */} {/* Adjusted icon size */} {ratings[key] !== undefined ? ratings[key] : 5}
{/* Flame Rating Input Component */} handleRatingChange(key, value)} />
))}
{/* Review Text - Using ReactQuill */}
{/* Add custom styles for Quill */}
{/* Spoiler Checkbox */}
setHasSpoilers(e.target.checked)} className="w-4 h-4 text-campfire-amber bg-campfire-dark border-campfire-ash rounded focus:ring-campfire-amber focus:ring-2 cursor-pointer" />
{/* Rating Chart Preview */}

Предварительный просмотр вашей оценки

{ if (characteristics.hasOwnProperty(key) && typeof value === 'number' && value >= 1 && value <= 10) { acc[key] = value; } return acc; }, {})} labels={characteristics} // Pass the characteristics labels size="medium" />
{/* Submit Button */}
{/* Added mt-6 for spacing */}
{/* Corrected error message condition */} {!isFormValid && (content.trim() === '' || content === '


' || !(isProgressSelect ? Object.keys(progressOptions).includes(progress) : progress.trim() !== '') || Object.keys(characteristics).some(key => typeof ratings[key] !== 'number' || ratings[key] < 1 || ratings[key] > 10) || (supportsSeasons && formSeasonId === undefined)) && (

Пожалуйста, заполните все обязательные поля (рецензия, прогресс, все оценки от 1 до 10{supportsSeasons ? ', сезон' : ''}).

)} {Object.keys(characteristics).length === 0 && (

Характеристики для этого произведения не загружены.

)}
); } export default ReviewForm;