|
|
@@ -51,6 +51,8 @@ export default function Charges() {
|
|
|
const [cancelingLeaseId, setCancelingLeaseId] = useState(null)
|
|
|
const [loading, setLoading] = useState(false)
|
|
|
const [editCharge, setEditCharge] = useState(null)
|
|
|
+ const [deductibleSummary, setDeductibleSummary] = useState(null)
|
|
|
+ const [showDeductible, setShowDeductible] = useState(false)
|
|
|
|
|
|
const emptyForm = {
|
|
|
property_id: '',
|
|
|
@@ -79,8 +81,16 @@ export default function Charges() {
|
|
|
setCharges(data)
|
|
|
}
|
|
|
|
|
|
+ const loadDeductibleSummary = async () => {
|
|
|
+ if (!filterProperty) { setDeductibleSummary(null); return }
|
|
|
+ try {
|
|
|
+ const { data } = await api.get(`/charges/deductible-summary/${filterProperty}`)
|
|
|
+ setDeductibleSummary(data)
|
|
|
+ } catch { setDeductibleSummary(null) }
|
|
|
+ }
|
|
|
+
|
|
|
useEffect(() => { loadProperties() }, [])
|
|
|
- useEffect(() => { if (filterProperty) loadCharges() }, [filterProperty, filterYear])
|
|
|
+ useEffect(() => { if (filterProperty) { loadCharges(); loadDeductibleSummary() } }, [filterProperty, filterYear])
|
|
|
|
|
|
const openAdd = () => {
|
|
|
setEditCharge(null)
|
|
|
@@ -415,6 +425,62 @@ export default function Charges() {
|
|
|
)}
|
|
|
</div>
|
|
|
|
|
|
+ {/* Deductible summary table (10 years) */}
|
|
|
+ {filterProperty && deductibleSummary && (
|
|
|
+ <div className="card mb-6">
|
|
|
+ <button
|
|
|
+ onClick={() => setShowDeductible(v => !v)}
|
|
|
+ className="w-full flex items-center justify-between text-left"
|
|
|
+ >
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <span className="text-lg">💼</span>
|
|
|
+ <div>
|
|
|
+ <h3 className="text-sm font-semibold text-purple-800">Charges déductibles des impôts — 10 ans</h3>
|
|
|
+ <p className="text-xs text-purple-500">
|
|
|
+ Total cumulé : <span className="font-bold">{fmt(deductibleSummary.grand_total)}</span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <span className={`text-gray-400 transition-transform ${showDeductible ? 'rotate-180' : ''}`}>▼</span>
|
|
|
+ </button>
|
|
|
+ {showDeductible && (
|
|
|
+ <div className="mt-4 overflow-x-auto">
|
|
|
+ <table className="w-full text-sm">
|
|
|
+ <thead>
|
|
|
+ <tr className="bg-purple-50 border-b border-purple-100">
|
|
|
+ <th className="text-left px-4 py-2 text-purple-700 font-semibold">Année</th>
|
|
|
+ <th className="text-right px-4 py-2 text-purple-700 font-semibold">Montant déductible</th>
|
|
|
+ <th className="text-right px-4 py-2 text-purple-700 font-semibold">Nb factures</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody className="divide-y divide-purple-50">
|
|
|
+ {deductibleSummary.years.map(y => (
|
|
|
+ <tr key={y.year} className={`hover:bg-purple-50/50 ${String(y.year) === filterYear ? 'bg-purple-50 font-semibold' : ''}`}>
|
|
|
+ <td className="px-4 py-2 text-gray-700">{y.year}</td>
|
|
|
+ <td className={`px-4 py-2 text-right ${y.total > 0 ? 'text-purple-700' : 'text-gray-300'}`}>
|
|
|
+ {y.total > 0 ? fmt(y.total) : '—'}
|
|
|
+ </td>
|
|
|
+ <td className={`px-4 py-2 text-right ${y.count > 0 ? 'text-gray-600' : 'text-gray-300'}`}>
|
|
|
+ {y.count > 0 ? y.count : '—'}
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ ))}
|
|
|
+ </tbody>
|
|
|
+ <tfoot className="bg-purple-50 border-t-2 border-purple-200">
|
|
|
+ <tr>
|
|
|
+ <td className="px-4 py-2 font-bold text-purple-800">Total (10 ans)</td>
|
|
|
+ <td className="px-4 py-2 text-right font-bold text-purple-800">{fmt(deductibleSummary.grand_total)}</td>
|
|
|
+ <td className="px-4 py-2 text-right font-bold text-gray-600">
|
|
|
+ {deductibleSummary.years.reduce((s, y) => s + y.count, 0)}
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tfoot>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
{/* Charges list */}
|
|
|
{charges.length === 0 ? (
|
|
|
<div className="card text-center py-12">
|