556 lines
26 KiB
TypeScript
556 lines
26 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import React, { useState, useEffect } from 'react';
|
||
|
|
import { useNotification } from './NotificationProvider';
|
||
|
|
import Link from 'next/link';
|
||
|
|
|
||
|
|
interface HistoriqueItem {
|
||
|
|
id: string;
|
||
|
|
date: string;
|
||
|
|
adherentNom: string;
|
||
|
|
montant: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface PrescripteurBudget {
|
||
|
|
id: string;
|
||
|
|
value: string;
|
||
|
|
montant: number;
|
||
|
|
consommé: number;
|
||
|
|
restant: number;
|
||
|
|
ajustementConsomme: number;
|
||
|
|
historique: HistoriqueItem[];
|
||
|
|
}
|
||
|
|
|
||
|
|
interface BudgetData {
|
||
|
|
global: {
|
||
|
|
totalAlloue: number;
|
||
|
|
totalConsomme: number;
|
||
|
|
totalRestant: number;
|
||
|
|
};
|
||
|
|
prescripteurs: PrescripteurBudget[];
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function BudgetContent() {
|
||
|
|
const { showNotification } = useNotification();
|
||
|
|
const [data, setData] = useState<BudgetData | null>(null);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [savingId, setSavingId] = useState<string | null>(null);
|
||
|
|
const [addModalPrescripteur, setAddModalPrescripteur] = useState<string | null>(null);
|
||
|
|
const [addAmount, setAddAmount] = useState('');
|
||
|
|
const [rectifierModalPrescripteur, setRectifierModalPrescripteur] = useState<PrescripteurBudget | null>(null);
|
||
|
|
const [rectifierBudget, setRectifierBudget] = useState('');
|
||
|
|
const [rectifierAjustement, setRectifierAjustement] = useState('');
|
||
|
|
const [expandedPrescripteur, setExpandedPrescripteur] = useState<string | null>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
fetchBudgets();
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const fetchBudgets = async () => {
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/budget');
|
||
|
|
if (response.ok) {
|
||
|
|
const res = await response.json();
|
||
|
|
setData(res);
|
||
|
|
} else {
|
||
|
|
showNotification('error', 'Erreur lors du chargement des budgets');
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
showNotification('error', 'Erreur lors du chargement des budgets');
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleAddMoney = async () => {
|
||
|
|
if (!addModalPrescripteur) return;
|
||
|
|
const raw = addAmount.replace(',', '.');
|
||
|
|
const montantToAdd = parseFloat(raw);
|
||
|
|
if (isNaN(montantToAdd) || montantToAdd <= 0) {
|
||
|
|
showNotification('error', 'Montant invalide');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setSavingId(addModalPrescripteur);
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/budget', {
|
||
|
|
method: 'PATCH',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify({ prescripteurValue: addModalPrescripteur, montantToAdd }),
|
||
|
|
});
|
||
|
|
if (response.ok) {
|
||
|
|
showNotification('success', 'Budget mis à jour');
|
||
|
|
setAddModalPrescripteur(null);
|
||
|
|
setAddAmount('');
|
||
|
|
fetchBudgets();
|
||
|
|
} else {
|
||
|
|
const res = await response.json();
|
||
|
|
showNotification('error', res.error || "Erreur lors de l'ajout");
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
showNotification('error', "Erreur lors de l'ajout");
|
||
|
|
} finally {
|
||
|
|
setSavingId(null);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleRectifier = async () => {
|
||
|
|
if (!rectifierModalPrescripteur) return;
|
||
|
|
const montant = parseFloat(rectifierBudget.replace(',', '.'));
|
||
|
|
const ajustement = parseFloat(rectifierAjustement.replace(',', '.'));
|
||
|
|
if (isNaN(montant) || montant < 0) {
|
||
|
|
showNotification('error', 'Budget invalide');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setSavingId(rectifierModalPrescripteur.value);
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/budget', {
|
||
|
|
method: 'PATCH',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify({
|
||
|
|
prescripteurValue: rectifierModalPrescripteur.value,
|
||
|
|
rectifier: true,
|
||
|
|
montant,
|
||
|
|
ajustementConsomme: isNaN(ajustement) ? 0 : ajustement,
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
if (response.ok) {
|
||
|
|
showNotification('success', 'Corrections enregistrées');
|
||
|
|
setRectifierModalPrescripteur(null);
|
||
|
|
setRectifierBudget('');
|
||
|
|
setRectifierAjustement('');
|
||
|
|
fetchBudgets();
|
||
|
|
} else {
|
||
|
|
const res = await response.json();
|
||
|
|
showNotification('error', res.error || 'Erreur lors de la correction');
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
showNotification('error', 'Erreur lors de la correction');
|
||
|
|
} finally {
|
||
|
|
setSavingId(null);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const formatEuro = (n: number) => n.toFixed(2).replace('.', ',') + ' €';
|
||
|
|
|
||
|
|
const formatDate = (dateStr: string) => {
|
||
|
|
return new Date(dateStr).toLocaleDateString('fr-FR', {
|
||
|
|
day: '2-digit',
|
||
|
|
month: 'short',
|
||
|
|
year: 'numeric',
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<div className="bg-white rounded-lg shadow-sm p-8">
|
||
|
|
<div className="text-center text-sm text-gray-500">Chargement...</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!data || data.prescripteurs.length === 0) {
|
||
|
|
return (
|
||
|
|
<div className="bg-white rounded-lg shadow-sm p-8">
|
||
|
|
<div className="text-center text-gray-500">
|
||
|
|
<p className="mb-4">Aucun prescripteur configuré.</p>
|
||
|
|
<p className="text-sm mb-6">
|
||
|
|
Ajoutez des prescripteurs dans la configuration pour pouvoir définir leurs budgets.
|
||
|
|
</p>
|
||
|
|
<Link
|
||
|
|
href="/dashboard/parametres/configuration"
|
||
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-lblue text-white rounded-lg hover:bg-dblue transition-colors text-sm font-medium"
|
||
|
|
>
|
||
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||
|
|
</svg>
|
||
|
|
Aller à la configuration
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const { global, prescripteurs } = data;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
{/* Blocs budget global - style dashboard */}
|
||
|
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-5">
|
||
|
|
<div className="bg-white rounded-xl shadow-sm p-4 sm:p-6 border border-gray-100 hover:shadow-lg hover:border-dyellow/30 transition-all duration-300 group">
|
||
|
|
<div className="flex items-start justify-between mb-3 sm:mb-4">
|
||
|
|
<div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-gradient-to-br from-dyellow/20 to-dyellow/10 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||
|
|
<svg className="w-5 h-5 sm:w-6 sm:h-6 text-dyellow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<p className="text-[10px] sm:text-xs text-gray-600 mb-1.5 sm:mb-2 font-medium uppercase tracking-wide">
|
||
|
|
Budget total alloué
|
||
|
|
</p>
|
||
|
|
<p className="text-2xl sm:text-3xl font-bold text-gray-900">{formatEuro(global.totalAlloue)}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-white rounded-xl shadow-sm p-4 sm:p-6 border border-gray-100 hover:shadow-lg hover:border-dyellow/30 transition-all duration-300 group">
|
||
|
|
<div className="flex items-start justify-between mb-3 sm:mb-4">
|
||
|
|
<div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-gradient-to-br from-orange-100 to-orange-50 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||
|
|
<svg className="w-5 h-5 sm:w-6 sm:h-6 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<p className="text-[10px] sm:text-xs text-gray-600 mb-1.5 sm:mb-2 font-medium uppercase tracking-wide">
|
||
|
|
Budget consommé
|
||
|
|
</p>
|
||
|
|
<p className="text-2xl sm:text-3xl font-bold text-orange-600">{formatEuro(global.totalConsomme)}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-white rounded-xl shadow-sm p-4 sm:p-6 border border-gray-100 hover:shadow-lg hover:border-lgreen/30 transition-all duration-300 group">
|
||
|
|
<div className="flex items-start justify-between mb-3 sm:mb-4">
|
||
|
|
<div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-gradient-to-br from-lgreen/20 to-lgreen/10 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||
|
|
<svg className="w-5 h-5 sm:w-6 sm:h-6 text-lgreen" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<p className="text-[10px] sm:text-xs text-gray-600 mb-1.5 sm:mb-2 font-medium uppercase tracking-wide">
|
||
|
|
Budget restant
|
||
|
|
</p>
|
||
|
|
<p className="text-2xl sm:text-3xl font-bold text-lgreen">{formatEuro(global.totalRestant)}</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Liste des budgets par prescripteur */}
|
||
|
|
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
|
||
|
|
<div className="px-6 py-4 border-b border-gray-200">
|
||
|
|
<h2 className="text-lg font-semibold text-gray-900">Budget par prescripteur</h2>
|
||
|
|
<p className="text-sm text-gray-500 mt-1">
|
||
|
|
Cliquez sur un prescripteur pour voir l'historique des participations. Utilisez « Ajouter » pour créditer le budget.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div className="overflow-x-auto">
|
||
|
|
<table className="min-w-full divide-y divide-gray-200">
|
||
|
|
<thead className="bg-gray-50">
|
||
|
|
<tr>
|
||
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
|
|
Prescripteur
|
||
|
|
</th>
|
||
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
|
|
Consommé
|
||
|
|
</th>
|
||
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||
|
|
Restant
|
||
|
|
</th>
|
||
|
|
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider w-56">
|
||
|
|
Actions
|
||
|
|
</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
||
|
|
{prescripteurs.map((item) => (
|
||
|
|
<React.Fragment key={item.value}>
|
||
|
|
<tr
|
||
|
|
key={item.value}
|
||
|
|
className="hover:bg-gray-50 cursor-pointer"
|
||
|
|
onClick={() => setExpandedPrescripteur(expandedPrescripteur === item.value ? null : item.value)}
|
||
|
|
>
|
||
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<span className="text-sm font-medium text-gray-900">{item.value}</span>
|
||
|
|
{item.historique.length > 0 && (
|
||
|
|
<span className="text-gray-400">
|
||
|
|
<svg
|
||
|
|
className={`w-4 h-4 transition-transform ${expandedPrescripteur === item.value ? 'rotate-90' : ''}`}
|
||
|
|
fill="none"
|
||
|
|
stroke="currentColor"
|
||
|
|
viewBox="0 0 24 24"
|
||
|
|
>
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||
|
|
</svg>
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-orange-600 font-medium">
|
||
|
|
{formatEuro(item.consommé)}
|
||
|
|
</td>
|
||
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
||
|
|
<span
|
||
|
|
className={`text-sm font-medium ${
|
||
|
|
item.consommé > item.montant || (item.montant > 0 && item.restant < item.montant * 0.2)
|
||
|
|
? 'text-red-600'
|
||
|
|
: 'text-lgreen'
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
{formatEuro(item.restant)}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
<td className="px-6 py-4 whitespace-nowrap text-right" onClick={(e) => e.stopPropagation()}>
|
||
|
|
<div className="flex items-center gap-2 justify-end">
|
||
|
|
<button
|
||
|
|
onClick={() => {
|
||
|
|
setAddModalPrescripteur(item.value);
|
||
|
|
setAddAmount('');
|
||
|
|
}}
|
||
|
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-lgreen rounded-lg hover:bg-dgreen transition-colors"
|
||
|
|
>
|
||
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||
|
|
</svg>
|
||
|
|
Ajouter
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={() => {
|
||
|
|
setRectifierModalPrescripteur(item);
|
||
|
|
setRectifierBudget(item.montant.toString().replace('.', ','));
|
||
|
|
setRectifierAjustement((item.ajustementConsomme ?? 0).toString().replace('.', ','));
|
||
|
|
}}
|
||
|
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-lblue rounded-lg hover:bg-dblue transition-colors"
|
||
|
|
>
|
||
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||
|
|
</svg>
|
||
|
|
Rectifier
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
{expandedPrescripteur === item.value && item.historique.length > 0 && (
|
||
|
|
<tr key={`${item.value}-hist`} className="bg-gray-50">
|
||
|
|
<td colSpan={4} className="px-6 py-4">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<h4 className="text-xs font-semibold text-gray-700 uppercase tracking-wide">
|
||
|
|
Historique des participations
|
||
|
|
</h4>
|
||
|
|
<div className="rounded-lg border border-gray-200 overflow-hidden">
|
||
|
|
<table className="min-w-full text-sm">
|
||
|
|
<thead className="bg-gray-100">
|
||
|
|
<tr>
|
||
|
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500">Date</th>
|
||
|
|
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500">Adhérent</th>
|
||
|
|
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500">Montant</th>
|
||
|
|
<th className="px-4 py-2 text-right text-xs font-medium text-gray-500 w-24">Lien</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody className="divide-y divide-gray-200 bg-white">
|
||
|
|
{item.historique.map((h) => (
|
||
|
|
<tr key={h.id}>
|
||
|
|
<td className="px-4 py-2 text-gray-700">{formatDate(h.date)}</td>
|
||
|
|
<td className="px-4 py-2 text-gray-700">{h.adherentNom}</td>
|
||
|
|
<td className="px-4 py-2 text-right text-orange-600 font-medium">
|
||
|
|
{formatEuro(h.montant)}
|
||
|
|
</td>
|
||
|
|
<td className="px-4 py-2 text-right">
|
||
|
|
<Link
|
||
|
|
href="/dashboard/factures"
|
||
|
|
className="text-lblue hover:underline text-xs"
|
||
|
|
>
|
||
|
|
Voir
|
||
|
|
</Link>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
))}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
)}
|
||
|
|
{expandedPrescripteur === item.value && item.historique.length === 0 && (
|
||
|
|
<tr className="bg-gray-50">
|
||
|
|
<td colSpan={4} className="px-6 py-4">
|
||
|
|
<p className="text-sm text-gray-500 italic">Aucune participation enregistrée pour ce prescripteur.</p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
)}
|
||
|
|
</React.Fragment>
|
||
|
|
))}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="px-6 py-3 bg-gray-50 border-t border-gray-200">
|
||
|
|
<p className="text-xs text-gray-500">
|
||
|
|
Les prescripteurs sont définis dans{' '}
|
||
|
|
<Link href="/dashboard/parametres/configuration" className="text-lblue hover:underline">
|
||
|
|
Paramètres → Configuration
|
||
|
|
</Link>
|
||
|
|
.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Modal Rectifier */}
|
||
|
|
{rectifierModalPrescripteur && (
|
||
|
|
<div
|
||
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"
|
||
|
|
onClick={() => {
|
||
|
|
setRectifierModalPrescripteur(null);
|
||
|
|
setRectifierBudget('');
|
||
|
|
setRectifierAjustement('');
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
className="bg-white rounded-xl shadow-xl max-w-sm w-full p-6"
|
||
|
|
onClick={(e) => e.stopPropagation()}
|
||
|
|
role="dialog"
|
||
|
|
aria-modal="true"
|
||
|
|
aria-labelledby="rectifier-budget-title"
|
||
|
|
>
|
||
|
|
<h3 id="rectifier-budget-title" className="text-lg font-semibold text-gray-900 mb-2">
|
||
|
|
Rectifier — {rectifierModalPrescripteur.value}
|
||
|
|
</h3>
|
||
|
|
<p className="text-sm text-gray-500 mb-4">
|
||
|
|
Corrigez le budget alloué et/ou l'ajustement du consommé en cas d'erreur.
|
||
|
|
</p>
|
||
|
|
<div className="space-y-4 mb-6">
|
||
|
|
<div>
|
||
|
|
<label className="block text-xs font-medium text-gray-700 mb-1.5">Budget alloué (€)</label>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
inputMode="decimal"
|
||
|
|
value={rectifierBudget}
|
||
|
|
onChange={(e) => setRectifierBudget(e.target.value.replace(/[^0-9.,]/g, ''))}
|
||
|
|
placeholder="0,00"
|
||
|
|
className="flex-1 px-4 py-2.5 text-sm text-gray-900 border border-gray-300 rounded-lg focus:ring-2 focus:ring-lblue focus:border-transparent"
|
||
|
|
/>
|
||
|
|
<span className="text-sm text-gray-500">€</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<label className="block text-xs font-medium text-gray-700 mb-1.5">
|
||
|
|
Ajustement consommé (€)
|
||
|
|
</label>
|
||
|
|
<p className="text-xs text-gray-500 mb-1">
|
||
|
|
Valeur à ajouter au consommé calculé. Négatif pour diminuer (ex: -6,80).
|
||
|
|
</p>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
inputMode="decimal"
|
||
|
|
value={rectifierAjustement}
|
||
|
|
onChange={(e) => setRectifierAjustement(e.target.value.replace(/[^0-9.,-]/g, ''))}
|
||
|
|
placeholder="0,00"
|
||
|
|
className="flex-1 px-4 py-2.5 text-sm text-gray-900 border border-gray-300 rounded-lg focus:ring-2 focus:ring-lblue focus:border-transparent"
|
||
|
|
/>
|
||
|
|
<span className="text-sm text-gray-500">€</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="flex gap-3 justify-end">
|
||
|
|
<button
|
||
|
|
onClick={() => {
|
||
|
|
setRectifierModalPrescripteur(null);
|
||
|
|
setRectifierBudget('');
|
||
|
|
setRectifierAjustement('');
|
||
|
|
}}
|
||
|
|
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"
|
||
|
|
>
|
||
|
|
Annuler
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={handleRectifier}
|
||
|
|
disabled={savingId === rectifierModalPrescripteur.value}
|
||
|
|
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-lblue rounded-lg hover:bg-dblue disabled:opacity-50 transition-colors"
|
||
|
|
>
|
||
|
|
{savingId === rectifierModalPrescripteur.value ? (
|
||
|
|
<>
|
||
|
|
<svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||
|
|
</svg>
|
||
|
|
Enregistrement...
|
||
|
|
</>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||
|
|
</svg>
|
||
|
|
Enregistrer
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Modal Ajouter de l'argent */}
|
||
|
|
{addModalPrescripteur && (
|
||
|
|
<div
|
||
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"
|
||
|
|
onClick={() => {
|
||
|
|
setAddModalPrescripteur(null);
|
||
|
|
setAddAmount('');
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
className="bg-white rounded-xl shadow-xl max-w-sm w-full p-6"
|
||
|
|
onClick={(e) => e.stopPropagation()}
|
||
|
|
role="dialog"
|
||
|
|
aria-modal="true"
|
||
|
|
aria-labelledby="add-budget-title"
|
||
|
|
>
|
||
|
|
<h3 id="add-budget-title" className="text-lg font-semibold text-gray-900 mb-2">
|
||
|
|
Ajouter du budget — {addModalPrescripteur}
|
||
|
|
</h3>
|
||
|
|
<p className="text-sm text-gray-500 mb-4">
|
||
|
|
Entrez le montant à ajouter au budget existant.
|
||
|
|
</p>
|
||
|
|
<div className="flex items-center gap-2 mb-6">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
inputMode="decimal"
|
||
|
|
value={addAmount}
|
||
|
|
onChange={(e) => setAddAmount(e.target.value.replace(/[^0-9.,]/g, ''))}
|
||
|
|
placeholder="0,00"
|
||
|
|
className="flex-1 px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-lblue focus:border-transparent"
|
||
|
|
autoFocus
|
||
|
|
/>
|
||
|
|
<span className="text-sm text-gray-500">€</span>
|
||
|
|
</div>
|
||
|
|
<div className="flex gap-3 justify-end">
|
||
|
|
<button
|
||
|
|
onClick={() => {
|
||
|
|
setAddModalPrescripteur(null);
|
||
|
|
setAddAmount('');
|
||
|
|
}}
|
||
|
|
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"
|
||
|
|
>
|
||
|
|
Annuler
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={handleAddMoney}
|
||
|
|
disabled={savingId === addModalPrescripteur}
|
||
|
|
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-lgreen rounded-lg hover:bg-dgreen disabled:opacity-50 transition-colors"
|
||
|
|
>
|
||
|
|
{savingId === addModalPrescripteur ? (
|
||
|
|
<>
|
||
|
|
<svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||
|
|
</svg>
|
||
|
|
Ajout...
|
||
|
|
</>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||
|
|
</svg>
|
||
|
|
Ajouter
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|