optimisation de la latence des pages ajout d'option clique droit plus paragraphe
293 lines
18 KiB
TypeScript
293 lines
18 KiB
TypeScript
'use client';
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import { BookProject, BookSettings } from '@/lib/types';
|
||
import { GENRES, TONES, POV_OPTIONS, TENSE_OPTIONS } from '@/lib/constants';
|
||
import { Settings, Book, Feather, Users, Clock, Target, Hash, Save, Check } from 'lucide-react';
|
||
import { useLanguage } from '@/providers/LanguageProvider';
|
||
import { TranslationKey } from '@/lib/i18n/translations';
|
||
|
||
interface BookSettingsProps {
|
||
project: BookProject;
|
||
onUpdate: (project: BookProject) => void;
|
||
onDeleteProject: () => void;
|
||
}
|
||
|
||
const DEFAULT_SETTINGS: BookSettings = {
|
||
genre: '',
|
||
subGenre: '',
|
||
targetAudience: '',
|
||
tone: '',
|
||
pov: '',
|
||
tense: '',
|
||
synopsis: '',
|
||
themes: ''
|
||
};
|
||
|
||
const BookSettingsComponent: React.FC<BookSettingsProps> = React.memo(({ project, onUpdate, onDeleteProject }) => {
|
||
const { t } = useLanguage();
|
||
|
||
// Local state for all editable fields to prevent excessive API calls
|
||
const [localTitle, setLocalTitle] = useState(project.title);
|
||
const [localAuthor, setLocalAuthor] = useState(project.author);
|
||
const [localStyleGuide, setLocalStyleGuide] = useState(project.styleGuide || '');
|
||
const [localSettings, setLocalSettings] = useState<BookSettings>(project.settings || DEFAULT_SETTINGS);
|
||
|
||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||
const [isSaving, setIsSaving] = useState(false);
|
||
const [showSavedFeedback, setShowSavedFeedback] = useState(false);
|
||
|
||
useEffect(() => {
|
||
setLocalTitle(project.title);
|
||
setLocalAuthor(project.author);
|
||
setLocalStyleGuide(project.styleGuide || '');
|
||
if (project.settings) {
|
||
setLocalSettings(project.settings);
|
||
}
|
||
}, [project.title, project.author, project.styleGuide, project.settings]);
|
||
|
||
const handleChange = (key: keyof BookSettings, value: string) => {
|
||
setLocalSettings(prev => ({ ...prev, [key]: value }));
|
||
};
|
||
|
||
const handleSave = () => {
|
||
setIsSaving(true);
|
||
onUpdate({
|
||
...project,
|
||
title: localTitle,
|
||
author: localAuthor,
|
||
styleGuide: localStyleGuide,
|
||
settings: localSettings
|
||
});
|
||
|
||
// Simulate save delay for UI feedback
|
||
setTimeout(() => {
|
||
setIsSaving(false);
|
||
setShowSavedFeedback(true);
|
||
setTimeout(() => setShowSavedFeedback(false), 2000);
|
||
}, 500);
|
||
};
|
||
|
||
return (
|
||
<div className="h-full bg-theme-bg p-8 overflow-y-auto transition-colors duration-300">
|
||
<div className="max-w-4xl mx-auto bg-theme-panel rounded-xl shadow-lg border border-theme-border overflow-hidden transition-colors duration-300 pb-8">
|
||
|
||
<div className="bg-slate-900 text-white p-6 border-b border-slate-800 flex items-center justify-between">
|
||
<div className="flex items-center gap-4">
|
||
<div className="bg-blue-600 p-3 rounded-lg">
|
||
<Settings size={24} />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-2xl font-bold">{t('book_settings.title')}</h2>
|
||
<p className="text-slate-400 text-sm">{t('book_settings.subtitle')}</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={handleSave}
|
||
disabled={isSaving}
|
||
className={`flex items-center gap-2 px-6 py-2.5 rounded-lg font-bold transition-all ${showSavedFeedback
|
||
? 'bg-green-600 hover:bg-green-700 text-white'
|
||
: 'bg-blue-600 hover:bg-blue-700 text-white'
|
||
}`}
|
||
>
|
||
{showSavedFeedback ? <Check size={18} /> : <Save size={18} />}
|
||
{showSavedFeedback ? t('book_settings.saved' as TranslationKey) || 'Sauvegardé' : t('book_settings.save' as TranslationKey) || 'Sauvegarder'}
|
||
</button>
|
||
</div>
|
||
|
||
<div className="p-8 space-y-8">
|
||
<section className="space-y-4">
|
||
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
|
||
<Book size={18} className="text-blue-600" /> {t('book_settings.basic_info')}
|
||
</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.novel_title')}</label>
|
||
<input
|
||
type="text"
|
||
value={localTitle}
|
||
onChange={(e) => setLocalTitle(e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none font-serif font-bold text-lg transition-colors duration-300"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.author_name')}</label>
|
||
<input
|
||
type="text"
|
||
value={localAuthor}
|
||
onChange={(e) => setLocalAuthor(e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.global_synopsis')}</label>
|
||
<textarea
|
||
value={localSettings.synopsis}
|
||
onChange={(e) => handleChange('synopsis', e.target.value)}
|
||
className="w-full p-3 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none h-24 text-sm transition-colors duration-300"
|
||
placeholder={t('book_settings.synopsis_placeholder')}
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="space-y-4">
|
||
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
|
||
<Target size={18} className="text-red-500" /> {t('book_settings.genre_audience')}
|
||
</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.main_genre')}</label>
|
||
<input
|
||
type="text"
|
||
list="genre-suggestions"
|
||
value={localSettings.genre}
|
||
onChange={(e) => handleChange('genre', e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
placeholder={t('book_settings.genre_placeholder')}
|
||
/>
|
||
<datalist id="genre-suggestions">
|
||
{GENRES.map(g => <option key={g} value={g} />)}
|
||
</datalist>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.sub_genre')}</label>
|
||
<input
|
||
type="text"
|
||
value={localSettings.subGenre || ''}
|
||
onChange={(e) => handleChange('subGenre', e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
placeholder={t('book_settings.subgenre_placeholder')}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.target_audience')}</label>
|
||
<input
|
||
type="text"
|
||
value={localSettings.targetAudience}
|
||
onChange={(e) => handleChange('targetAudience', e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
placeholder={t('book_settings.audience_placeholder')}
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.key_themes')}</label>
|
||
<div className="relative">
|
||
<Hash size={14} className="absolute left-3 top-3 text-theme-muted" />
|
||
<input
|
||
type="text"
|
||
value={localSettings.themes}
|
||
onChange={(e) => handleChange('themes', e.target.value)}
|
||
className="w-full pl-9 p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
placeholder={t('book_settings.themes_placeholder')}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="space-y-4">
|
||
<h3 className="text-lg font-bold text-theme-text flex items-center gap-2 border-b border-theme-border pb-2">
|
||
<Feather size={18} className="text-purple-600" /> {t('book_settings.narration_style')}
|
||
</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1 flex items-center gap-1">
|
||
<Users size={14} /> {t('book_settings.pov')}
|
||
</label>
|
||
<select
|
||
value={localSettings.pov}
|
||
onChange={(e) => handleChange('pov', e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
>
|
||
<option value="">{t('book_settings.select')}</option>
|
||
{POV_OPTIONS.map(o => <option key={o} value={o}>{t(`pov_options.${o.toLowerCase().replace(/\s+/g, '_')}` as TranslationKey) || o}</option>)}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1 flex items-center gap-1">
|
||
<Clock size={14} /> {t('book_settings.tense')}
|
||
</label>
|
||
<select
|
||
value={localSettings.tense}
|
||
onChange={(e) => handleChange('tense', e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
>
|
||
<option value="">{t('book_settings.select')}</option>
|
||
{TENSE_OPTIONS.map(o => <option key={o} value={o}>{t(`tense_options.${o.toLowerCase().replace(/\s+/g, '_')}` as TranslationKey) || o}</option>)}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">{t('book_settings.general_tone')}</label>
|
||
<input
|
||
type="text"
|
||
list="tone-suggestions"
|
||
value={localSettings.tone}
|
||
onChange={(e) => handleChange('tone', e.target.value)}
|
||
className="w-full p-2.5 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-colors duration-300"
|
||
placeholder={t('book_settings.tone_placeholder')}
|
||
/>
|
||
<datalist id="tone-suggestions">
|
||
{TONES.map(tOption => <option key={tOption} value={tOption} />)}
|
||
</datalist>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-4">
|
||
<label className="block text-sm font-semibold text-theme-muted mb-1">
|
||
{t('book_settings.style_guide')}
|
||
</label>
|
||
<p className="text-xs text-theme-muted mb-2">
|
||
{t('book_settings.style_guide_help')}
|
||
</p>
|
||
<textarea
|
||
value={localStyleGuide}
|
||
onChange={(e) => setLocalStyleGuide(e.target.value)}
|
||
className="w-full p-3 bg-theme-bg text-theme-text border border-theme-border rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none h-32 text-sm font-mono transition-colors duration-300"
|
||
placeholder={t('book_settings.style_guide_placeholder')}
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="space-y-4 pt-8 border-t border-red-200">
|
||
<h3 className="text-lg font-bold text-red-600 flex items-center gap-2 pb-2">
|
||
<span className="bg-red-100 p-1 rounded">⚠️</span> {t('book_settings.danger_zone')}
|
||
</h3>
|
||
<div className="bg-red-50 border border-red-200 rounded-lg p-6">
|
||
<h4 className="font-bold text-red-900 mb-2">{t('book_settings.delete_project')}</h4>
|
||
<p className="text-sm text-red-700 mb-4">
|
||
{t('book_settings.delete_warning')}
|
||
</p>
|
||
{showDeleteConfirm ? (
|
||
<div className="flex items-center gap-4 bg-theme-panel p-4 rounded border border-red-200">
|
||
<span className="text-sm font-bold text-theme-text">{t('book_settings.are_you_sure')}</span>
|
||
<button
|
||
onClick={onDeleteProject}
|
||
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 text-sm font-bold opacity-90 transition-opacity"
|
||
>
|
||
{t('book_settings.confirm_delete')}
|
||
</button>
|
||
<button
|
||
onClick={() => setShowDeleteConfirm(false)}
|
||
className="px-4 py-2 bg-theme-bg text-theme-text border border-theme-border rounded hover:opacity-80 text-sm transition-opacity"
|
||
>
|
||
{t('book_settings.cancel')}
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<button
|
||
onClick={() => setShowDeleteConfirm(true)}
|
||
className="px-4 py-2 bg-theme-panel border border-red-300 text-red-600 rounded hover:bg-red-50 text-sm font-bold transition-colors duration-300"
|
||
>
|
||
{t('book_settings.delete_button')}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
});
|
||
|
||
export default BookSettingsComponent; |