Update ExtraordinaryAdmin.tsx

This commit is contained in:
2025-12-11 00:14:54 +01:00
committed by GitHub
parent 4b63183003
commit 32adfbbc58

View File

@@ -2,7 +2,7 @@
import React, { useEffect, useState } from 'react';
import { CondoService } from '../services/mockDb';
import { ExtraordinaryExpense, Family, ExpenseItem, ExpenseShare } from '../types';
import { Plus, Calendar, FileText, CheckCircle2, Clock, Users, X, Save, Paperclip, Euro, Trash2, Eye, Briefcase, Pencil, Banknote } from 'lucide-react';
import { Plus, Calendar, FileText, CheckCircle2, Clock, Users, X, Save, Paperclip, Euro, Trash2, Eye, Briefcase, Pencil, Banknote, History } from 'lucide-react';
export const ExtraordinaryAdmin: React.FC = () => {
const [expenses, setExpenses] = useState<ExtraordinaryExpense[]>([]);
@@ -32,6 +32,8 @@ export const ExtraordinaryAdmin: React.FC = () => {
const [payAmount, setPayAmount] = useState<number>(0);
const [payNotes, setPayNotes] = useState('');
const [isPaying, setIsPaying] = useState(false);
const [paymentHistory, setPaymentHistory] = useState<any[]>([]);
const [loadingHistory, setLoadingHistory] = useState(false);
useEffect(() => {
loadData();
@@ -67,9 +69,6 @@ export const ExtraordinaryAdmin: React.FC = () => {
// Preserve existing data if available
const existing = formShares.find(s => s.familyId === fid);
if (existing) {
// If editing and we just toggled someone else, re-calc percentages evenly?
// Or keep manual adjustments?
// For simplicity: auto-recalc resets percentages evenly.
return {
...existing,
percentage: parseFloat(percentage.toFixed(2)),
@@ -120,14 +119,11 @@ export const ExtraordinaryAdmin: React.FC = () => {
setFormItems(newItems);
};
// Trigger share recalc when total changes (if not manual)
// We only trigger auto-recalc if not editing existing complex shares,
// OR if editing but user hasn't manually messed with them yet (simplification: always recalc on total change for now)
useEffect(() => {
if (selectedFamilyIds.length > 0) {
recalculateShares(selectedFamilyIds);
}
}, [totalAmount]); // eslint-disable-line react-hooks/exhaustive-deps
}, [totalAmount]);
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
@@ -154,7 +150,6 @@ export const ExtraordinaryAdmin: React.FC = () => {
};
const openEditModal = async (exp: ExtraordinaryExpense) => {
// Fetch full details first to get items and shares
try {
const detail = await CondoService.getExpenseDetails(exp.id);
setIsEditing(true);
@@ -166,12 +161,10 @@ export const ExtraordinaryAdmin: React.FC = () => {
setFormContractor(detail.contractorName);
setFormItems(detail.items || []);
// Populate shares for editing
const currentShares = detail.shares || [];
setFormShares(currentShares);
setSelectedFamilyIds(currentShares.map(s => s.familyId));
// Attachments (Cannot edit attachments in this simple view for now, cleared)
setFormAttachments([]);
setShowModal(true);
@@ -197,7 +190,7 @@ export const ExtraordinaryAdmin: React.FC = () => {
endDate: formEnd,
contractorName: formContractor,
items: formItems,
shares: formShares // Now we send shares to sync
shares: formShares
});
} else {
await CondoService.createExpense({
@@ -237,12 +230,25 @@ export const ExtraordinaryAdmin: React.FC = () => {
};
// MANUAL PAYMENT HANDLERS
const openPayModal = (share: ExpenseShare) => {
const openPayModal = async (share: ExpenseShare) => {
if (!selectedExpense) return;
const remaining = Math.max(0, share.amountDue - share.amountPaid);
setPayShare(share);
setPayAmount(remaining);
setPayNotes('Saldo manuale');
setShowPayModal(true);
// Fetch History
setLoadingHistory(true);
try {
const history = await CondoService.getExpensePayments(selectedExpense.id, share.familyId);
setPaymentHistory(history);
} catch (e) {
console.error(e);
setPaymentHistory([]);
} finally {
setLoadingHistory(false);
}
};
const handleManualPayment = async (e: React.FormEvent) => {
@@ -251,21 +257,48 @@ export const ExtraordinaryAdmin: React.FC = () => {
setIsPaying(true);
try {
// Updated API allows Admins to pass familyId to pay on their behalf
await CondoService.payExpense(selectedExpense.id, payAmount, payShare.familyId);
// Refresh Details
const updated = await CondoService.getExpenseDetails(selectedExpense.id);
setSelectedExpense(updated);
setShowPayModal(false);
await refreshShareData();
// Reset input
setPayAmount(0);
setPayNotes('');
} catch (e: any) {
console.error(e);
alert("Errore registrazione pagamento: " + (e.message || "Errore sconosciuto"));
} finally {
setIsPaying(false);
}
};
const handleDeletePayment = async (paymentId: string) => {
if (!confirm("Annullare questo pagamento? L'importo verrà stornato dal saldo versato.")) return;
try {
await CondoService.deleteExpensePayment(paymentId);
await refreshShareData();
} catch(e) {
alert("Errore annullamento pagamento");
}
};
const refreshShareData = async () => {
if (!selectedExpense || !payShare) return;
// Refresh Expense Details to update the table in background
const updatedExpense = await CondoService.getExpenseDetails(selectedExpense.id);
setSelectedExpense(updatedExpense);
// Refresh Payment History inside modal
const history = await CondoService.getExpensePayments(selectedExpense.id, payShare.familyId);
setPaymentHistory(history);
// Update local PayShare reference for amount calculation
const updatedShare = updatedExpense.shares?.find(s => s.familyId === payShare.familyId);
if (updatedShare) {
setPayShare(updatedShare);
setPayAmount(Math.max(0, updatedShare.amountDue - updatedShare.amountPaid));
}
};
return (
<div className="space-y-6 pb-20 animate-fade-in">
<div className="flex justify-between items-center">
@@ -497,15 +530,13 @@ export const ExtraordinaryAdmin: React.FC = () => {
</span>
</td>
<td className="p-3 text-center">
{share.status !== 'PAID' && (
<button
onClick={() => openPayModal(share)}
className="text-xs bg-blue-50 text-blue-600 hover:bg-blue-100 px-2 py-1 rounded border border-blue-200 font-medium"
title="Registra Pagamento Manuale"
className="text-xs bg-slate-100 text-slate-700 hover:bg-slate-200 px-3 py-1 rounded border border-slate-200 font-medium transition-colors"
title="Gestisci Pagamenti"
>
Paga
Gestisci
</button>
)}
</td>
</tr>
))}
@@ -528,23 +559,82 @@ export const ExtraordinaryAdmin: React.FC = () => {
</div>
)}
{/* MANUAL PAYMENT MODAL */}
{/* MANUAL PAYMENT / MANAGEMENT MODAL */}
{showPayModal && payShare && (
<div className="fixed inset-0 bg-black/50 z-[60] flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-xl shadow-xl w-full max-w-sm p-6 animate-in fade-in zoom-in duration-200">
<div className="flex justify-between items-center mb-4 border-b pb-2">
<h3 className="font-bold text-lg text-slate-800">Registra Pagamento</h3>
<div className="bg-white rounded-xl shadow-xl w-full max-w-md p-0 animate-in fade-in zoom-in duration-200 overflow-hidden flex flex-col max-h-[90vh]">
<div className="flex justify-between items-center p-4 border-b bg-slate-50">
<div>
<h3 className="font-bold text-lg text-slate-800">Gestione Pagamenti</h3>
<p className="text-xs text-slate-500">{payShare.familyName}</p>
</div>
<button onClick={() => setShowPayModal(false)}><X className="w-5 h-5 text-slate-400"/></button>
</div>
<form onSubmit={handleManualPayment} className="space-y-4">
<div className="bg-blue-50 p-3 rounded-lg border border-blue-100 text-sm">
<p className="text-slate-500">Famiglia: <span className="font-bold text-slate-800">{payShare.familyName}</span></p>
<p className="text-slate-500">Restante da pagare: <span className="font-bold text-red-600"> {(payShare.amountDue - payShare.amountPaid).toFixed(2)}</span></p>
<div className="p-6 overflow-y-auto">
{/* Summary Card */}
<div className="bg-blue-50 p-4 rounded-xl border border-blue-100 mb-6 text-sm flex justify-between items-center">
<div>
<p className="text-slate-500 text-xs uppercase font-bold">Quota Totale</p>
<p className="font-bold text-slate-800 text-lg"> {payShare.amountDue.toFixed(2)}</p>
</div>
<div className="text-right">
<p className="text-slate-500 text-xs uppercase font-bold">Restante</p>
<p className="font-bold text-red-600 text-lg"> {(payShare.amountDue - payShare.amountPaid).toFixed(2)}</p>
</div>
</div>
{/* History Section */}
<div className="mb-6">
<h4 className="text-xs font-bold text-slate-500 uppercase mb-2 flex items-center gap-1">
<History className="w-3 h-3"/> Storico Versamenti
</h4>
{loadingHistory ? (
<div className="text-center py-4 text-xs text-slate-400">Caricamento...</div>
) : paymentHistory.length === 0 ? (
<div className="text-center py-4 bg-slate-50 rounded border border-dashed border-slate-200 text-xs text-slate-400">
Nessun pagamento registrato.
</div>
) : (
<div className="border rounded-lg overflow-hidden">
<table className="w-full text-xs">
<thead className="bg-slate-50 text-slate-500">
<tr>
<th className="p-2 text-left">Data</th>
<th className="p-2 text-left">Note</th>
<th className="p-2 text-right">Importo</th>
<th className="p-2"></th>
</tr>
</thead>
<tbody className="divide-y">
{paymentHistory.map(ph => (
<tr key={ph.id}>
<td className="p-2 text-slate-600">{new Date(ph.datePaid).toLocaleDateString()}</td>
<td className="p-2 text-slate-500 truncate max-w-[100px]" title={ph.notes}>{ph.notes}</td>
<td className="p-2 text-right font-bold text-slate-700"> {ph.amount.toFixed(2)}</td>
<td className="p-2 text-center">
<button
onClick={() => handleDeletePayment(ph.id)}
className="text-red-400 hover:text-red-600 p-1 hover:bg-red-50 rounded"
title="Annulla pagamento"
>
<Trash2 className="w-3 h-3"/>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
{/* Add New Section */}
<div className="border-t pt-4">
<h4 className="text-sm font-bold text-slate-800 mb-3">Registra Nuovo Incasso</h4>
<form onSubmit={handleManualPayment} className="space-y-4">
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Importo Incassato ()</label>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Importo ()</label>
<div className="relative">
<Banknote className="absolute left-3 top-2.5 w-4 h-4 text-slate-400"/>
<input
@@ -568,15 +658,14 @@ export const ExtraordinaryAdmin: React.FC = () => {
/>
</div>
<div className="flex gap-2 pt-2">
<button type="button" onClick={() => setShowPayModal(false)} className="flex-1 border p-2 rounded-lg text-slate-600 hover:bg-slate-50">Annulla</button>
<button type="submit" disabled={isPaying} className="flex-1 bg-green-600 text-white p-2 rounded-lg hover:bg-green-700 font-bold disabled:opacity-50">
{isPaying ? '...' : 'Conferma'}
<button type="submit" disabled={isPaying || payAmount <= 0} className="w-full bg-green-600 text-white p-2.5 rounded-lg hover:bg-green-700 font-bold disabled:opacity-50 flex items-center justify-center gap-2">
{isPaying ? 'Registrazione...' : <><Plus className="w-4 h-4"/> Conferma Pagamento</>}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)}
</div>
);