124 lines
4.3 KiB
JavaScript
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>
|
|
);
|
|
}
|