Implement ScriptReviewSection component

- Created ScriptReviewSection component with script segment display
- Added speaker dropdown for each segment to allow editing assignments
- Implemented Generate Audiobook and Skip Review buttons
- Styled for readability and mobile responsiveness with Tailwind CSS
- Text segments displayed as read-only (only speaker assignment is editable)
- Follows existing component patterns from VoiceAssignmentSection
- Validates Requirements 15.3, 15.4, 15.5, 15.6, 15.8
This commit is contained in:
2025-12-09 11:53:04 +00:00
parent d737b1f7ea
commit bc768f4f45
2 changed files with 112 additions and 1 deletions

View File

@@ -0,0 +1,111 @@
import type { Role, ScriptSegment } from '../api/tts';
interface ScriptReviewSectionProps {
segments: ScriptSegment[];
roles: Role[];
onSegmentChange: (segmentId: string, newSpeaker: string) => void;
onGenerate: () => void;
onSkipReview: () => void;
isGenerating: boolean;
}
export default function ScriptReviewSection({
segments,
roles,
onSegmentChange,
onGenerate,
onSkipReview,
isGenerating,
}: ScriptReviewSectionProps) {
const handleSpeakerChange = (segmentId: string, newSpeaker: string) => {
onSegmentChange(segmentId, newSpeaker);
};
return (
<div className="w-full max-w-4xl mx-auto p-4 sm:p-6">
<h2 className="text-xl sm:text-2xl font-bold mb-3 sm:mb-4">Review Script</h2>
<p className="text-sm sm:text-base text-gray-600 mb-4 sm:mb-6">
Review the AI-generated script and adjust speaker assignments as needed.
</p>
{/* Script Segments */}
<div className="space-y-4 sm:space-y-6 mb-6 sm:mb-8">
{segments.map((segment, index) => (
<div
key={segment.id}
className="border-2 border-gray-300 rounded-lg p-3 sm:p-4 bg-white shadow-sm"
>
{/* Segment Header */}
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 mb-3">
<span className="text-xs sm:text-sm font-semibold text-gray-500">
Segment {index + 1}
</span>
{/* Speaker Dropdown */}
<div className="flex items-center gap-2 flex-1">
<label className="text-xs sm:text-sm font-medium text-gray-700 whitespace-nowrap">
Speaker:
</label>
<select
value={segment.speaker}
onChange={(e) => handleSpeakerChange(segment.id, e.target.value)}
className="flex-1 p-2 sm:p-2 min-h-[44px] border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none text-sm sm:text-base touch-manipulation"
disabled={isGenerating}
>
{roles.map((role) => (
<option key={role.id} value={role.id}>
{role.label}
</option>
))}
</select>
</div>
</div>
{/* Segment Text */}
<div className="w-full min-h-[80px] p-3 border-2 border-gray-200 rounded-lg bg-gray-50 text-sm sm:text-base whitespace-pre-wrap">
{segment.text}
</div>
</div>
))}
</div>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center">
<button
onClick={onSkipReview}
disabled={isGenerating}
className={`w-full sm:w-auto px-6 py-3 min-h-[44px] rounded-lg font-semibold transition-colors touch-manipulation ${
isGenerating
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-gray-500 text-white hover:bg-gray-600 active:bg-gray-700 cursor-pointer'
}`}
>
Skip Review
</button>
<button
onClick={onGenerate}
disabled={isGenerating}
className={`w-full sm:w-auto px-8 py-3 min-h-[44px] rounded-lg font-semibold text-white transition-colors touch-manipulation ${
isGenerating
? 'bg-gray-400 cursor-not-allowed'
: 'bg-green-600 hover:bg-green-700 active:bg-green-800 cursor-pointer'
}`}
>
{isGenerating ? 'Generating...' : 'Generate Audiobook'}
</button>
</div>
{/* Info Message */}
{!isGenerating && (
<div className="mt-4 sm:mt-6 p-3 sm:p-4 bg-blue-50 border border-blue-200 text-blue-800 rounded text-sm sm:text-base">
<p className="font-semibold mb-1">💡 Tip:</p>
<p>
Review the AI-generated speaker assignments and make any necessary corrections before generating the audiobook.
</p>
</div>
)}
</div>
);
}