refactoring majeur (code dupliqué, mort, ...)

- Économie : ~1240 lignes de code dupliqué
- Réduction : ~60% du code modal
- Amélioration : Cohérence et maintenabilité
This commit is contained in:
Yannick Le Duc
2025-08-27 12:45:37 +02:00
parent 6acc7d9d35
commit dc388bf371
25 changed files with 1446 additions and 1821 deletions

View File

@@ -1,11 +1,5 @@
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { campaignService } from '@/lib/services';
import { MarkdownEditor } from '@/components/MarkdownEditor';
import CampaignFormModal from './base/CampaignFormModal';
interface CreateCampaignModalProps {
isOpen: boolean;
@@ -14,205 +8,12 @@ interface CreateCampaignModalProps {
}
export default function CreateCampaignModal({ isOpen, onClose, onSuccess }: CreateCampaignModalProps) {
const [formData, setFormData] = useState({
title: '',
description: '',
budget_per_user: '',
spending_tiers: ''
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
try {
await campaignService.create({
title: formData.title,
description: formData.description,
budget_per_user: parseInt(formData.budget_per_user),
spending_tiers: formData.spending_tiers,
status: 'deposit'
});
onSuccess();
setFormData({ title: '', description: '', budget_per_user: '', spending_tiers: '' });
} catch (err) {
setError('Erreur lors de la création de la campagne');
console.error(err);
} finally {
setLoading(false);
}
};
const generateOptimalTiers = (budget: number): string => {
if (budget <= 0) return "0";
// Cas spéciaux pour des budgets courants
if (budget === 10000) {
return "0, 500, 1000, 2000, 3000, 5000, 7500, 10000";
}
if (budget === 8000) {
return "0, 500, 1000, 2000, 3000, 4000, 6000, 8000";
}
const tiers = [0];
// Déterminer les paliers "ronds" selon la taille du budget
let roundValues: number[] = [];
if (budget <= 100) {
// Petits budgets : multiples de 5, 10, 25
roundValues = [5, 10, 25, 50, 75, 100];
} else if (budget <= 500) {
// Budgets moyens : multiples de 25, 50, 100
roundValues = [25, 50, 75, 100, 150, 200, 250, 300, 400, 500];
} else if (budget <= 2000) {
// Budgets moyens-grands : multiples de 100, 250, 500
roundValues = [100, 250, 500, 750, 1000, 1250, 1500, 1750, 2000];
} else if (budget <= 10000) {
// Gros budgets : multiples de 500, 1000, 2000
roundValues = [500, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 6000, 7500, 10000];
} else {
// Très gros budgets : multiples de 1000, 2000, 5000
roundValues = [1000, 2000, 3000, 5000, 7500, 10000, 15000, 20000, 25000, 50000];
}
// Sélectionner les paliers qui sont inférieurs ou égaux au budget
const validTiers = roundValues.filter(tier => tier <= budget);
// Prendre 6-8 paliers intermédiaires + 0 et le budget final
const targetCount = Math.min(8, Math.max(6, validTiers.length));
const step = Math.max(1, Math.floor(validTiers.length / targetCount));
for (let i = 0; i < validTiers.length && tiers.length < targetCount + 1; i += step) {
if (!tiers.includes(validTiers[i])) {
tiers.push(validTiers[i]);
}
}
// Ajouter le budget final s'il n'est pas déjà présent
if (!tiers.includes(budget)) {
tiers.push(budget);
}
// Trier et retourner
return tiers.sort((a, b) => a - b).join(', ');
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleBudgetBlur = (e: React.FocusEvent<HTMLInputElement>) => {
const budget = parseInt(e.target.value);
if (!isNaN(budget) && budget > 0 && !formData.spending_tiers) {
setFormData(prev => ({
...prev,
spending_tiers: generateOptimalTiers(budget)
}));
}
};
const handleClose = () => {
setFormData({
title: '',
description: '',
budget_per_user: '',
spending_tiers: ''
});
setError('');
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Créer une nouvelle campagne</DialogTitle>
<DialogDescription>
Configurez les paramètres de votre campagne de budget participatif.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
</div>
)}
<div className="space-y-2">
<Label htmlFor="title">Titre de la campagne *</Label>
<Input
id="title"
name="title"
value={formData.title}
onChange={handleChange}
placeholder="Ex: Amélioration des espaces verts"
required
/>
</div>
<MarkdownEditor
value={formData.description}
onChange={(value) => setFormData(prev => ({ ...prev, description: value }))}
placeholder="Décrivez l'objectif de cette campagne..."
label="Description *"
maxLength={2000}
/>
<div className="space-y-2">
<Label htmlFor="budget_per_user">Budget () *</Label>
<Input
id="budget_per_user"
name="budget_per_user"
type="number"
value={formData.budget_per_user}
onChange={handleChange}
onBlur={handleBudgetBlur}
placeholder="100"
min="1"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="spending_tiers">Paliers de dépense *</Label>
<Input
id="spending_tiers"
name="spending_tiers"
value={formData.spending_tiers}
onChange={handleChange}
placeholder="Ex: 0, 10, 25, 50, 100"
required
/>
<p className="text-xs text-slate-500 dark:text-slate-400">
Séparez les montants par des virgules (ex: 0, 10, 25, 50, 100)
{formData.budget_per_user && !formData.spending_tiers && (
<span className="block mt-1 text-blue-600 dark:text-blue-400">
💡 Les paliers seront générés automatiquement après avoir saisi le budget
</span>
)}
</p>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={handleClose}>
Annuler
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Création...' : 'Créer la campagne'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<CampaignFormModal
isOpen={isOpen}
onClose={onClose}
onSuccess={onSuccess}
mode="create"
/>
);
}