Evite les doublons dans les emails lors d'import de participants
Set version to 0.2.0 (et affiche le en footer)
This commit is contained in:
@@ -218,9 +218,11 @@ DECLARE
|
|||||||
counter INTEGER := 0;
|
counter INTEGER := 0;
|
||||||
max_attempts INTEGER := 10;
|
max_attempts INTEGER := 10;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Convertir le titre en slug (minuscules, remplacer espaces par tirets, supprimer caractères spéciaux)
|
-- Convertir le titre en slug (minuscules, supprimer accents, remplacer espaces par tirets, supprimer caractères spéciaux)
|
||||||
base_slug := lower(regexp_replace(title, '[^a-zA-Z0-9\s]', '', 'g'));
|
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, '\s+', '-', 'g');
|
||||||
|
base_slug := regexp_replace(base_slug, '-+', '-', 'g');
|
||||||
base_slug := trim(both '-' from base_slug);
|
base_slug := trim(both '-' from base_slug);
|
||||||
|
|
||||||
-- Si le slug est vide, utiliser un slug par défaut
|
-- Si le slug est vide, utiliser un slug par défaut
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mes-budgets-participatifs",
|
"name": "mes-budgets-participatifs",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ function CampaignParticipantsPageContent() {
|
|||||||
|
|
||||||
const handleImportParticipants = async (data: any[]) => {
|
const handleImportParticipants = async (data: any[]) => {
|
||||||
try {
|
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 => ({
|
const participantsToCreate = data.map(row => ({
|
||||||
campaign_id: campaignId,
|
campaign_id: campaignId,
|
||||||
first_name: row.Prénom || '',
|
first_name: row.Prénom || '',
|
||||||
@@ -94,11 +98,24 @@ function CampaignParticipantsPageContent() {
|
|||||||
email: row.Email || ''
|
email: row.Email || ''
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Créer les participants un par un
|
// Filtrer les participants pour éviter les doublons d'email
|
||||||
for (const participant of participantsToCreate) {
|
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);
|
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();
|
loadData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors de l\'import des participants:', error);
|
console.error('Erreur lors de l\'import des participants:', error);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
|
|
||||||
import AuthGuard from '@/components/AuthGuard';
|
import AuthGuard from '@/components/AuthGuard';
|
||||||
import Footer from '@/components/Footer';
|
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 { FolderOpen, Users, FileText, Plus, BarChart3, Settings, Check, Copy, Mail, Share2 } from 'lucide-react';
|
||||||
import StatusSwitch from '@/components/StatusSwitch';
|
import StatusSwitch from '@/components/StatusSwitch';
|
||||||
import { MarkdownContent } from '@/components/MarkdownContent';
|
import { MarkdownContent } from '@/components/MarkdownContent';
|
||||||
@@ -499,6 +500,9 @@ function AdminPageContent() {
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
||||||
|
{/* Version Display */}
|
||||||
|
<VersionDisplay />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { PROJECT_CONFIG } from '@/lib/project.config';
|
import { PROJECT_CONFIG } from '@/lib/project.config';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
|
import VersionDisplay from '@/components/VersionDisplay';
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -196,6 +197,9 @@ export default function HomePage() {
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<Footer variant="home" />
|
<Footer variant="home" />
|
||||||
|
|
||||||
|
{/* Version Display */}
|
||||||
|
<VersionDisplay />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -225,9 +225,11 @@ DECLARE
|
|||||||
counter INTEGER := 0;
|
counter INTEGER := 0;
|
||||||
max_attempts INTEGER := 10;
|
max_attempts INTEGER := 10;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Convertir le titre en slug (minuscules, remplacer espaces par tirets, supprimer caractères spéciaux)
|
-- Convertir le titre en slug (minuscules, supprimer accents, remplacer espaces par tirets, supprimer caractères spéciaux)
|
||||||
base_slug := lower(regexp_replace(title, '[^a-zA-Z0-9\s]', '', 'g'));
|
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, '\s+', '-', 'g');
|
||||||
|
base_slug := regexp_replace(base_slug, '-+', '-', 'g');
|
||||||
base_slug := trim(both '-' from base_slug);
|
base_slug := trim(both '-' from base_slug);
|
||||||
|
|
||||||
-- Si le slug est vide, utiliser un slug par défaut
|
-- Si le slug est vide, utiliser un slug par défaut
|
||||||
|
|||||||
28
src/components/VersionDisplay.tsx
Normal file
28
src/components/VersionDisplay.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export default function VersionDisplay() {
|
||||||
|
const [version, setVersion] = useState<string>('');
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="text-center py-2">
|
||||||
|
<span className="text-xs text-slate-400 dark:text-slate-500">
|
||||||
|
v{version}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,10 +5,15 @@ import { emailService } from './email';
|
|||||||
|
|
||||||
// Fonction utilitaire pour générer un slug côté client
|
// Fonction utilitaire pour générer un slug côté client
|
||||||
function generateSlugClient(title: string): string {
|
function generateSlugClient(title: string): string {
|
||||||
// Convertir en minuscules et remplacer les caractères spéciaux
|
// Convertir en minuscules, supprimer les accents et remplacer les caractères spéciaux
|
||||||
let slug = title.toLowerCase()
|
let slug = title
|
||||||
.replace(/[^a-z0-9\s]/g, '')
|
.toLowerCase()
|
||||||
.replace(/\s+/g, '-')
|
.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();
|
.trim();
|
||||||
|
|
||||||
// Si le slug est vide, utiliser 'campagne'
|
// Si le slug est vide, utiliser 'campagne'
|
||||||
|
|||||||
Reference in New Issue
Block a user