- améliore l'export/import (format de fichiers en paramètres, amélioration de la robustesse, )
- ajout bouton tout effacer des propositions et participants
This commit is contained in:
@@ -9,6 +9,7 @@ import EditParticipantModal from '@/components/EditParticipantModal';
|
||||
import DeleteParticipantModal from '@/components/DeleteParticipantModal';
|
||||
import ImportFileModal from '@/components/ImportFileModal';
|
||||
import SendParticipantEmailModal from '@/components/SendParticipantEmailModal';
|
||||
import ClearAllParticipantsModal from '@/components/ClearAllParticipantsModal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -31,6 +32,7 @@ function CampaignParticipantsPageContent() {
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showImportModal, setShowImportModal] = useState(false);
|
||||
const [showSendEmailModal, setShowSendEmailModal] = useState(false);
|
||||
const [showClearAllModal, setShowClearAllModal] = useState(false);
|
||||
const [selectedParticipant, setSelectedParticipant] = useState<Participant | null>(null);
|
||||
const [copiedParticipantId, setCopiedParticipantId] = useState<string | null>(null);
|
||||
|
||||
@@ -87,9 +89,9 @@ function CampaignParticipantsPageContent() {
|
||||
try {
|
||||
const participantsToCreate = data.map(row => ({
|
||||
campaign_id: campaignId,
|
||||
first_name: row.first_name || '',
|
||||
last_name: row.last_name || '',
|
||||
email: row.email || ''
|
||||
first_name: row.Prénom || '',
|
||||
last_name: row.Nom || '',
|
||||
email: row.Email || ''
|
||||
}));
|
||||
|
||||
// Créer les participants un par un
|
||||
@@ -103,6 +105,16 @@ function CampaignParticipantsPageContent() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearAllParticipants = async () => {
|
||||
try {
|
||||
await participantService.deleteAllByCampaign(campaignId);
|
||||
loadData();
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la suppression des participants:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getInitials = (firstName: string, lastName: string) => {
|
||||
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
||||
};
|
||||
@@ -182,6 +194,16 @@ function CampaignParticipantsPageContent() {
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
Importer
|
||||
</Button>
|
||||
{participants.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowClearAllModal(true)}
|
||||
className="text-red-600 border-red-200 hover:bg-red-50 hover:border-red-300 dark:text-red-400 dark:border-red-800 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<User className="w-4 h-4 mr-2" />
|
||||
Tout effacer
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={() => setShowAddModal(true)} size="lg">
|
||||
✨ Nouveau participant
|
||||
</Button>
|
||||
@@ -377,6 +399,14 @@ function CampaignParticipantsPageContent() {
|
||||
campaign={campaign}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ClearAllParticipantsModal
|
||||
isOpen={showClearAllModal}
|
||||
onClose={() => setShowClearAllModal(false)}
|
||||
onConfirm={handleClearAllParticipants}
|
||||
campaignTitle={campaign?.title}
|
||||
participantCount={participants.length}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,8 @@ import AddPropositionModal from '@/components/AddPropositionModal';
|
||||
import EditPropositionModal from '@/components/EditPropositionModal';
|
||||
import DeletePropositionModal from '@/components/DeletePropositionModal';
|
||||
import ImportFileModal from '@/components/ImportFileModal';
|
||||
import ExportPropositionsButton from '@/components/ExportPropositionsButton';
|
||||
import ClearAllPropositionsModal from '@/components/ClearAllPropositionsModal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
@@ -29,6 +31,7 @@ function CampaignPropositionsPageContent() {
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showImportModal, setShowImportModal] = useState(false);
|
||||
const [showClearAllModal, setShowClearAllModal] = useState(false);
|
||||
const [selectedProposition, setSelectedProposition] = useState<Proposition | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -84,11 +87,11 @@ function CampaignPropositionsPageContent() {
|
||||
try {
|
||||
const propositionsToCreate = data.map(row => ({
|
||||
campaign_id: campaignId,
|
||||
title: row.title || '',
|
||||
description: row.description || '',
|
||||
author_first_name: row.author_first_name || 'admin',
|
||||
author_last_name: row.author_last_name || 'admin',
|
||||
author_email: row.author_email || 'admin@example.com'
|
||||
title: row.Titre || '',
|
||||
description: row.Description || '',
|
||||
author_first_name: row.Prénom || 'admin',
|
||||
author_last_name: row.Nom || 'admin',
|
||||
author_email: row.Email || 'admin@example.com'
|
||||
}));
|
||||
|
||||
// Créer les propositions une par une
|
||||
@@ -102,7 +105,15 @@ function CampaignPropositionsPageContent() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleClearAllPropositions = async () => {
|
||||
try {
|
||||
await propositionService.deleteAllByCampaign(campaignId);
|
||||
loadData();
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la suppression des propositions:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getInitials = (firstName: string, lastName: string) => {
|
||||
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
||||
@@ -171,6 +182,20 @@ function CampaignPropositionsPageContent() {
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
Importer
|
||||
</Button>
|
||||
<ExportPropositionsButton
|
||||
propositions={propositions}
|
||||
campaignTitle={campaign.title}
|
||||
/>
|
||||
{propositions.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowClearAllModal(true)}
|
||||
className="text-red-600 border-red-200 hover:bg-red-50 hover:border-red-300 dark:text-red-400 dark:border-red-800 dark:hover:bg-red-900/20"
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
Tout effacer
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={() => setShowAddModal(true)} size="lg">
|
||||
✨ Nouvelle proposition
|
||||
</Button>
|
||||
@@ -299,6 +324,14 @@ function CampaignPropositionsPageContent() {
|
||||
type="propositions"
|
||||
campaignTitle={campaign?.title}
|
||||
/>
|
||||
|
||||
<ClearAllPropositionsModal
|
||||
isOpen={showClearAllModal}
|
||||
onClose={() => setShowClearAllModal(false)}
|
||||
onConfirm={handleClearAllPropositions}
|
||||
campaignTitle={campaign?.title}
|
||||
propositionCount={propositions.length}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { authService } from '@/lib/auth';
|
||||
import CreateCampaignModal from '@/components/CreateCampaignModal';
|
||||
import EditCampaignModal from '@/components/EditCampaignModal';
|
||||
import DeleteCampaignModal from '@/components/DeleteCampaignModal';
|
||||
import ShareModal from '@/components/ShareModal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -14,7 +15,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
|
||||
import AuthGuard from '@/components/AuthGuard';
|
||||
import Footer from '@/components/Footer';
|
||||
import { FolderOpen, Users, FileText, Plus, BarChart3, Settings, Check, Copy, Mail } from 'lucide-react';
|
||||
import { FolderOpen, Users, FileText, Plus, BarChart3, Settings, Check, Copy, Mail, Share2 } from 'lucide-react';
|
||||
import StatusSwitch from '@/components/StatusSwitch';
|
||||
import { MarkdownContent } from '@/components/MarkdownContent';
|
||||
|
||||
@@ -27,6 +28,7 @@ function AdminPageContent() {
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showShareModal, setShowShareModal] = useState(false);
|
||||
const [selectedCampaign, setSelectedCampaign] = useState<Campaign | null>(null);
|
||||
|
||||
const [copiedCampaignId, setCopiedCampaignId] = useState<string | null>(null);
|
||||
@@ -433,6 +435,18 @@ function AdminPageContent() {
|
||||
<Copy className="w-3 h-3" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 w-7 p-0 text-slate-400 hover:text-slate-600"
|
||||
onClick={() => {
|
||||
setSelectedCampaign(campaign);
|
||||
setShowShareModal(true);
|
||||
}}
|
||||
title="Partager le lien"
|
||||
>
|
||||
<Share2 className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -474,6 +488,14 @@ function AdminPageContent() {
|
||||
{selectedCampaign && (
|
||||
<DeleteCampaignModal isOpen={showDeleteModal} onClose={() => setShowDeleteModal(false)} onSuccess={handleCampaignDeleted} campaign={selectedCampaign} />
|
||||
)}
|
||||
{selectedCampaign && (
|
||||
<ShareModal
|
||||
isOpen={showShareModal}
|
||||
onClose={() => setShowShareModal(false)}
|
||||
campaignTitle={selectedCampaign.title}
|
||||
depositUrl={`${window.location.origin}/p/${selectedCampaign.slug || 'campagne'}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<Footer />
|
||||
|
||||
@@ -12,6 +12,7 @@ import Footer from '@/components/Footer';
|
||||
import SmtpSettingsForm from '@/components/SmtpSettingsForm';
|
||||
import { Settings, Monitor, Save, CheckCircle, Mail, FileText, Download } from 'lucide-react';
|
||||
import { ExportAnonymizationSelect, AnonymizationLevel } from '@/components/ExportAnonymizationSelect';
|
||||
import { ExportFileFormatSelect, ExportFileFormat } from '@/components/ExportFileFormatSelect';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -24,6 +25,18 @@ function SettingsPageContent() {
|
||||
const [proposePageMessage, setProposePageMessage] = useState('');
|
||||
const [footerMessage, setFooterMessage] = useState('');
|
||||
const [exportAnonymization, setExportAnonymization] = useState<AnonymizationLevel>('full');
|
||||
const [exportFileFormat, setExportFileFormat] = useState<ExportFileFormat>('ods');
|
||||
|
||||
// États pour la détection des modifications
|
||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||
const [originalValues, setOriginalValues] = useState<{
|
||||
randomizePropositions: boolean;
|
||||
proposePageMessage: string;
|
||||
footerMessage: string;
|
||||
exportAnonymization: AnonymizationLevel;
|
||||
exportFileFormat: ExportFileFormat;
|
||||
} | null>(null);
|
||||
const [autoSaved, setAutoSaved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Vérifier la configuration Supabase
|
||||
@@ -42,6 +55,34 @@ function SettingsPageContent() {
|
||||
loadSettings();
|
||||
}, []);
|
||||
|
||||
// Détecter les modifications
|
||||
useEffect(() => {
|
||||
if (!originalValues) return;
|
||||
|
||||
const hasChanges =
|
||||
randomizePropositions !== originalValues.randomizePropositions ||
|
||||
proposePageMessage !== originalValues.proposePageMessage ||
|
||||
footerMessage !== originalValues.footerMessage ||
|
||||
exportAnonymization !== originalValues.exportAnonymization ||
|
||||
exportFileFormat !== originalValues.exportFileFormat;
|
||||
|
||||
setHasUnsavedChanges(hasChanges);
|
||||
}, [randomizePropositions, proposePageMessage, footerMessage, exportAnonymization, exportFileFormat, originalValues]);
|
||||
|
||||
// Avertissement avant de quitter la page
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
if (hasUnsavedChanges) {
|
||||
e.preventDefault();
|
||||
e.returnValue = 'Vous avez des modifications non sauvegardées. Êtes-vous sûr de vouloir quitter ?';
|
||||
return e.returnValue;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
}, [hasUnsavedChanges]);
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -63,6 +104,19 @@ function SettingsPageContent() {
|
||||
// Charger le niveau d'anonymisation des exports
|
||||
const anonymizationValue = await settingsService.getStringValue('export_anonymization', 'full') as AnonymizationLevel;
|
||||
setExportAnonymization(anonymizationValue);
|
||||
|
||||
// Charger le format de fichier d'export
|
||||
const fileFormatValue = await settingsService.getStringValue('export_file_format', 'ods') as ExportFileFormat;
|
||||
setExportFileFormat(fileFormatValue);
|
||||
|
||||
// Stocker les valeurs originales pour la détection des modifications
|
||||
setOriginalValues({
|
||||
randomizePropositions: randomizeValue,
|
||||
proposePageMessage: messageValue,
|
||||
footerMessage: footerValue,
|
||||
exportAnonymization: anonymizationValue,
|
||||
exportFileFormat: fileFormatValue
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des paramètres:', error);
|
||||
} finally {
|
||||
@@ -72,17 +126,82 @@ function SettingsPageContent() {
|
||||
|
||||
const handleRandomizeChange = async (checked: boolean) => {
|
||||
setRandomizePropositions(checked);
|
||||
// Sauvegarde automatique pour ce paramètre
|
||||
try {
|
||||
await settingsService.setBooleanValue('randomize_propositions', checked);
|
||||
// Mettre à jour les valeurs originales
|
||||
if (originalValues) {
|
||||
setOriginalValues({
|
||||
...originalValues,
|
||||
randomizePropositions: checked
|
||||
});
|
||||
}
|
||||
// Afficher la confirmation de sauvegarde automatique
|
||||
setAutoSaved(true);
|
||||
setTimeout(() => setAutoSaved(false), 2000);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la sauvegarde automatique:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportAnonymizationChange = async (value: AnonymizationLevel) => {
|
||||
setExportAnonymization(value);
|
||||
// Sauvegarde automatique pour ce paramètre
|
||||
try {
|
||||
await settingsService.setStringValue('export_anonymization', value);
|
||||
// Mettre à jour les valeurs originales
|
||||
if (originalValues) {
|
||||
setOriginalValues({
|
||||
...originalValues,
|
||||
exportAnonymization: value
|
||||
});
|
||||
}
|
||||
// Afficher la confirmation de sauvegarde automatique
|
||||
setAutoSaved(true);
|
||||
setTimeout(() => setAutoSaved(false), 2000);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la sauvegarde automatique:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportFileFormatChange = async (value: ExportFileFormat) => {
|
||||
setExportFileFormat(value);
|
||||
// Sauvegarde automatique pour ce paramètre
|
||||
try {
|
||||
await settingsService.setStringValue('export_file_format', value);
|
||||
// Mettre à jour les valeurs originales
|
||||
if (originalValues) {
|
||||
setOriginalValues({
|
||||
...originalValues,
|
||||
exportFileFormat: value
|
||||
});
|
||||
}
|
||||
// Afficher la confirmation de sauvegarde automatique
|
||||
setAutoSaved(true);
|
||||
setTimeout(() => setAutoSaved(false), 2000);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la sauvegarde automatique:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
await settingsService.setBooleanValue('randomize_propositions', randomizePropositions);
|
||||
// Sauvegarder seulement les paramètres qui ne sont pas sauvegardés automatiquement
|
||||
await settingsService.setStringValue('propose_page_message', proposePageMessage);
|
||||
await settingsService.setStringValue('footer_message', footerMessage);
|
||||
await settingsService.setStringValue('export_anonymization', exportAnonymization);
|
||||
|
||||
// Mettre à jour les valeurs originales
|
||||
setOriginalValues({
|
||||
randomizePropositions,
|
||||
proposePageMessage,
|
||||
footerMessage,
|
||||
exportAnonymization,
|
||||
exportFileFormat
|
||||
});
|
||||
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
setTimeout(() => setSaved(false), 3000); // Message plus long pour les textes
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la sauvegarde des paramètres:', error);
|
||||
} finally {
|
||||
@@ -115,13 +234,36 @@ function SettingsPageContent() {
|
||||
<div className="mb-8">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100">Paramètres</h1>
|
||||
<p className="text-slate-600 dark:text-slate-300 mt-2">Configurez les paramètres de l'application</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100">Paramètres</h1>
|
||||
{hasUnsavedChanges && (
|
||||
<div className="flex items-center gap-2 px-3 py-1 bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 rounded-full text-sm font-medium">
|
||||
<div className="w-2 h-2 bg-orange-500 rounded-full animate-pulse"></div>
|
||||
Modifications non sauvegardées
|
||||
</div>
|
||||
)}
|
||||
{autoSaved && (
|
||||
<div className="flex items-center gap-2 px-3 py-1 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 rounded-full text-sm font-medium">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
Sauvegardé automatiquement
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-slate-600 dark:text-slate-300 mt-2">
|
||||
{hasUnsavedChanges
|
||||
? 'Vous avez des modifications non sauvegardées. N\'oubliez pas de cliquer sur "Sauvegarder".'
|
||||
: 'Configurez les paramètres de l\'application'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="flex items-center gap-2"
|
||||
disabled={saving || !hasUnsavedChanges}
|
||||
className={`flex items-center gap-2 ${
|
||||
hasUnsavedChanges
|
||||
? 'bg-orange-600 hover:bg-orange-700 text-white'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
{saving ? (
|
||||
<>
|
||||
@@ -207,13 +349,25 @@ function SettingsPageContent() {
|
||||
Ce texte apparaît sous le titre de la campagne pour inviter les utilisateurs à déposer des propositions.
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
id="propose-page-message"
|
||||
value={proposePageMessage}
|
||||
onChange={(e) => setProposePageMessage(e.target.value)}
|
||||
className="w-full min-h-[100px] p-3 border border-slate-200 dark:border-slate-700 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 resize-y"
|
||||
placeholder="Entrez votre message d'invitation..."
|
||||
/>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
id="propose-page-message"
|
||||
value={proposePageMessage}
|
||||
onChange={(e) => setProposePageMessage(e.target.value)}
|
||||
className={`w-full min-h-[100px] p-3 border rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 resize-y ${
|
||||
originalValues && proposePageMessage !== originalValues.proposePageMessage
|
||||
? 'border-orange-300 dark:border-orange-600 bg-orange-50 dark:bg-orange-900/20'
|
||||
: 'border-slate-200 dark:border-slate-700'
|
||||
}`}
|
||||
placeholder="Entrez votre message d'invitation..."
|
||||
/>
|
||||
{originalValues && proposePageMessage !== originalValues.proposePageMessage && (
|
||||
<div className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300 rounded text-xs font-medium">
|
||||
<div className="w-1.5 h-1.5 bg-orange-500 rounded-full animate-pulse"></div>
|
||||
Modifié
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Message Setting */}
|
||||
@@ -226,13 +380,25 @@ function SettingsPageContent() {
|
||||
Ce texte apparaît en bas des pages publiques. Vous pouvez utiliser <code className="bg-slate-100 dark:bg-slate-700 px-1 rounded text-xs">[texte du lien](GITURL)</code> pour insérer un lien vers le repository Git.
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
<div className="relative">
|
||||
<textarea
|
||||
id="footer-message"
|
||||
value={footerMessage}
|
||||
onChange={(e) => setFooterMessage(e.target.value)}
|
||||
className="w-full min-h-[80px] p-3 border border-slate-200 dark:border-slate-700 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 resize-y"
|
||||
className={`w-full min-h-[80px] p-3 border rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 resize-y ${
|
||||
originalValues && footerMessage !== originalValues.footerMessage
|
||||
? 'border-orange-300 dark:border-orange-600 bg-orange-50 dark:bg-orange-900/20'
|
||||
: 'border-slate-200 dark:border-slate-700'
|
||||
}`}
|
||||
placeholder="Entrez votre message de bas de page..."
|
||||
/>
|
||||
{originalValues && footerMessage !== originalValues.footerMessage && (
|
||||
<div className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 bg-orange-100 dark:bg-orange-900/50 text-orange-700 dark:text-orange-300 rounded text-xs font-medium">
|
||||
<div className="w-1.5 h-1.5 bg-orange-500 rounded-full animate-pulse"></div>
|
||||
Modifié
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -253,10 +419,14 @@ function SettingsPageContent() {
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="p-4 bg-slate-50 dark:bg-slate-800 rounded-lg">
|
||||
<div className="p-4 bg-slate-50 dark:bg-slate-800 rounded-lg space-y-6">
|
||||
<ExportAnonymizationSelect
|
||||
value={exportAnonymization}
|
||||
onValueChange={setExportAnonymization}
|
||||
onValueChange={handleExportAnonymizationChange}
|
||||
/>
|
||||
<ExportFileFormatSelect
|
||||
value={exportFileFormat}
|
||||
onValueChange={handleExportFileFormatChange}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user