Files
plume/src/components/BookSettings.tsx
streaper2 12de3a8328 mie a jour de la des mots du jour,
optimisation de la latence des pages
ajout d'option  clique droit plus paragraphe
2026-03-14 20:45:59 +01:00

293 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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;