diff --git a/database/supabase-schema.sql b/database/supabase-schema.sql
index 52c3b55..e9eb8c3 100644
--- a/database/supabase-schema.sql
+++ b/database/supabase-schema.sql
@@ -218,9 +218,11 @@ DECLARE
counter INTEGER := 0;
max_attempts INTEGER := 10;
BEGIN
- -- Convertir le titre en slug (minuscules, remplacer espaces par tirets, supprimer caractères spéciaux)
- base_slug := lower(regexp_replace(title, '[^a-zA-Z0-9\s]', '', 'g'));
+ -- Convertir le titre en slug (minuscules, supprimer accents, remplacer espaces par tirets, supprimer caractères spéciaux)
+ base_slug := lower(unaccent(title));
+ base_slug := regexp_replace(base_slug, '[^a-z0-9\s-]', '', 'g');
base_slug := regexp_replace(base_slug, '\s+', '-', 'g');
+ base_slug := regexp_replace(base_slug, '-+', '-', 'g');
base_slug := trim(both '-' from base_slug);
-- Si le slug est vide, utiliser un slug par défaut
diff --git a/package.json b/package.json
index c1fa8d1..08d7492 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mes-budgets-participatifs",
- "version": "0.1.0",
+ "version": "0.2.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
diff --git a/src/app/admin/campaigns/[id]/participants/page.tsx b/src/app/admin/campaigns/[id]/participants/page.tsx
index ee5f9da..1939089 100644
--- a/src/app/admin/campaigns/[id]/participants/page.tsx
+++ b/src/app/admin/campaigns/[id]/participants/page.tsx
@@ -87,6 +87,10 @@ function CampaignParticipantsPageContent() {
const handleImportParticipants = async (data: any[]) => {
try {
+ // Récupérer les participants existants pour vérifier les emails
+ const existingParticipants = await participantService.getByCampaign(campaignId);
+ const existingEmails = new Set(existingParticipants.map(p => p.email.toLowerCase()));
+
const participantsToCreate = data.map(row => ({
campaign_id: campaignId,
first_name: row.Prénom || '',
@@ -94,11 +98,24 @@ function CampaignParticipantsPageContent() {
email: row.Email || ''
}));
- // Créer les participants un par un
- for (const participant of participantsToCreate) {
+ // Filtrer les participants pour éviter les doublons d'email
+ const newParticipants = participantsToCreate.filter(participant => {
+ const email = participant.email.toLowerCase();
+ return email && !existingEmails.has(email);
+ });
+
+ const skippedCount = participantsToCreate.length - newParticipants.length;
+
+ // Créer les nouveaux participants un par un
+ for (const participant of newParticipants) {
await participantService.create(participant);
}
+ // Afficher un message informatif si des participants ont été ignorés
+ if (skippedCount > 0) {
+ alert(`${skippedCount} participant(s) ignoré(s) car leur email existe déjà dans la campagne.`);
+ }
+
loadData();
} catch (error) {
console.error('Erreur lors de l\'import des participants:', error);
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 29ccce7..c61b8af 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -15,6 +15,7 @@ import { Badge } from '@/components/ui/badge';
import AuthGuard from '@/components/AuthGuard';
import Footer from '@/components/Footer';
+import VersionDisplay from '@/components/VersionDisplay';
import { FolderOpen, Users, FileText, Plus, BarChart3, Settings, Check, Copy, Mail, Share2 } from 'lucide-react';
import StatusSwitch from '@/components/StatusSwitch';
import { MarkdownContent } from '@/components/MarkdownContent';
@@ -499,6 +500,9 @@ function AdminPageContent() {
{/* Footer */}
+
+ {/* Version Display */}
+
);
diff --git a/src/app/page.tsx b/src/app/page.tsx
index e83b08b..ea12ea1 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -8,6 +8,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Badge } from '@/components/ui/badge';
import { PROJECT_CONFIG } from '@/lib/project.config';
import Footer from '@/components/Footer';
+import VersionDisplay from '@/components/VersionDisplay';
export default function HomePage() {
const router = useRouter();
@@ -196,6 +197,9 @@ export default function HomePage() {
{/* Footer */}
+
+ {/* Version Display */}
+
);
diff --git a/src/components/SqlSchemaDisplay.tsx b/src/components/SqlSchemaDisplay.tsx
index 49e9bba..ba7ff0f 100644
--- a/src/components/SqlSchemaDisplay.tsx
+++ b/src/components/SqlSchemaDisplay.tsx
@@ -225,9 +225,11 @@ DECLARE
counter INTEGER := 0;
max_attempts INTEGER := 10;
BEGIN
- -- Convertir le titre en slug (minuscules, remplacer espaces par tirets, supprimer caractères spéciaux)
- base_slug := lower(regexp_replace(title, '[^a-zA-Z0-9\s]', '', 'g'));
+ -- Convertir le titre en slug (minuscules, supprimer accents, remplacer espaces par tirets, supprimer caractères spéciaux)
+ base_slug := lower(unaccent(title));
+ base_slug := regexp_replace(base_slug, '[^a-z0-9\s-]', '', 'g');
base_slug := regexp_replace(base_slug, '\s+', '-', 'g');
+ base_slug := regexp_replace(base_slug, '-+', '-', 'g');
base_slug := trim(both '-' from base_slug);
-- Si le slug est vide, utiliser un slug par défaut
diff --git a/src/components/VersionDisplay.tsx b/src/components/VersionDisplay.tsx
new file mode 100644
index 0000000..62deffb
--- /dev/null
+++ b/src/components/VersionDisplay.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+export default function VersionDisplay() {
+ const [version, setVersion] = useState('');
+
+ useEffect(() => {
+ // Récupérer la version depuis package.json
+ fetch('/package.json')
+ .then(response => response.json())
+ .then(data => setVersion(data.version))
+ .catch(() => {
+ // Fallback si le fichier n'est pas accessible
+ setVersion('0.2.0');
+ });
+ }, []);
+
+ if (!version) return null;
+
+ return (
+
+
+ v{version}
+
+
+ );
+}
diff --git a/src/lib/services.ts b/src/lib/services.ts
index 988ad43..7303666 100644
--- a/src/lib/services.ts
+++ b/src/lib/services.ts
@@ -5,10 +5,15 @@ import { emailService } from './email';
// Fonction utilitaire pour générer un slug côté client
function generateSlugClient(title: string): string {
- // Convertir en minuscules et remplacer les caractères spéciaux
- let slug = title.toLowerCase()
- .replace(/[^a-z0-9\s]/g, '')
- .replace(/\s+/g, '-')
+ // Convertir en minuscules, supprimer les accents et remplacer les caractères spéciaux
+ let slug = title
+ .toLowerCase()
+ .normalize('NFD')
+ .replace(/[\u0300-\u036f]/g, '') // Supprime les accents
+ .replace(/[^a-z0-9\s-]/g, '') // Garde seulement lettres, chiffres, espaces et tirets
+ .replace(/\s+/g, '-') // Remplace les espaces par des tirets
+ .replace(/-+/g, '-') // Remplace les tirets multiples par un seul
+ .replace(/^-+|-+$/g, '') // Supprime les tirets en début et fin
.trim();
// Si le slug est vide, utiliser 'campagne'