groupe-meditation/frontend/src/pages/ConfigRepartition.jsx
2026-03-04 14:01:00 -05:00

124 lines
4.3 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { api } from '../stores/api';
import PageHeader from '../components/PageHeader';
import SuccessCheck from '../components/SuccessCheck';
export default function ConfigRepartition() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState('');
useEffect(() => {
api.get('/config-repartition/')
.then(setItems)
.catch(() => {})
.finally(() => setLoading(false));
}, []);
const total = items.reduce((sum, i) => sum + (parseFloat(i.pourcentage) || 0), 0);
const updatePct = (idx, val) => {
const updated = [...items];
updated[idx] = { ...updated[idx], pourcentage: val };
setItems(updated);
};
const handleSave = async () => {
if (Math.abs(total - 100) > 0.01) {
setError(`Le total doit être 100%. Actuellement: ${total.toFixed(2)}%`);
return;
}
setSaving(true);
setError('');
try {
await api.request('PUT', '/config-repartition/', {
items: items.map((i) => ({
destination: i.destination,
pourcentage: parseFloat(i.pourcentage),
})),
});
setSuccess(true);
} catch (e) {
setError(e.message);
setSaving(false);
}
};
if (success) return <SuccessCheck message="Répartition enregistrée ✓" />;
return (
<div className="min-h-screen bg-gray-100">
<PageHeader titre="Config. répartition" sousTitre="G8 — % des contributions" />
<div className="p-4 space-y-4">
{loading && <p className="text-center text-gray-400 py-8">Chargement...</p>}
<div className="bg-blue-50 rounded-2xl p-4">
<p className="text-xs text-blue-600">
💡 Répartition recommandée : District 30%, Région 30%, BSG 30%, Intergroupe 10%.
Le total doit toujours être 100%.
</p>
</div>
{items.map((item, idx) => (
<div key={item.destination} className="bg-white rounded-2xl p-4 shadow-sm">
<div className="flex items-center justify-between mb-2">
<p className="font-bold text-sm">{item.destination_label}</p>
<p className="text-xs text-gray-400">depuis {item.actif_depuis}</p>
</div>
<div className="flex items-center gap-3">
<input
type="range"
min="0"
max="100"
step="1"
value={parseFloat(item.pourcentage) || 0}
onChange={(e) => updatePct(idx, parseFloat(e.target.value))}
className="flex-1 accent-aa-blue"
/>
<div className="relative w-20">
<input
type="number"
inputMode="decimal"
step="0.5"
min="0"
max="100"
value={item.pourcentage}
onChange={(e) => updatePct(idx, e.target.value)}
className="w-full p-2 bg-gray-50 rounded-xl text-center font-bold border-0 focus:ring-2 focus:ring-aa-blue"
/>
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm">%</span>
</div>
</div>
</div>
))}
{/* Total */}
{items.length > 0 && (
<div className={`rounded-2xl p-4 text-center font-bold text-lg ${
Math.abs(total - 100) < 0.01
? 'bg-green-50 text-green-700'
: 'bg-red-50 text-red-700'
}`}>
Total : {total.toFixed(1)}%
{Math.abs(total - 100) >= 0.01 && (
<p className="text-xs font-normal mt-1">Le total doit être exactement 100%</p>
)}
</div>
)}
{error && <p className="text-red-500 text-sm text-center bg-red-50 p-3 rounded-lg">{error}</p>}
<button
onClick={handleSave}
disabled={saving || Math.abs(total - 100) >= 0.01}
className="w-full p-5 bg-aa-blue text-white rounded-2xl text-xl font-bold disabled:opacity-40 active:scale-[0.97] transition shadow-lg"
>
{saving ? '...' : '⚙️ Enregistrer la répartition'}
</button>
</div>
</div>
);
}