Update ExtraordinaryAdmin.tsx
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { CondoService } from '../services/mockDb';
|
import { CondoService } from '../services/mockDb';
|
||||||
import { ExtraordinaryExpense, Family, ExpenseItem, ExpenseShare } from '../types';
|
import { ExtraordinaryExpense, Family, ExpenseItem, ExpenseShare } from '../types';
|
||||||
import { Plus, Calendar, FileText, CheckCircle2, Clock, Users, X, Save, Paperclip, Euro, Trash2, Eye, Briefcase, Pencil } from 'lucide-react';
|
import { Plus, Calendar, FileText, CheckCircle2, Clock, Users, X, Save, Paperclip, Euro, Trash2, Eye, Briefcase, Pencil, Banknote } from 'lucide-react';
|
||||||
|
|
||||||
export const ExtraordinaryAdmin: React.FC = () => {
|
export const ExtraordinaryAdmin: React.FC = () => {
|
||||||
const [expenses, setExpenses] = useState<ExtraordinaryExpense[]>([]);
|
const [expenses, setExpenses] = useState<ExtraordinaryExpense[]>([]);
|
||||||
@@ -26,6 +26,13 @@ export const ExtraordinaryAdmin: React.FC = () => {
|
|||||||
const [formAttachments, setFormAttachments] = useState<{fileName: string, fileType: string, data: string}[]>([]);
|
const [formAttachments, setFormAttachments] = useState<{fileName: string, fileType: string, data: string}[]>([]);
|
||||||
const [selectedFamilyIds, setSelectedFamilyIds] = useState<string[]>([]); // Helper to track checkboxes
|
const [selectedFamilyIds, setSelectedFamilyIds] = useState<string[]>([]); // Helper to track checkboxes
|
||||||
|
|
||||||
|
// Manual Payment Modal State
|
||||||
|
const [showPayModal, setShowPayModal] = useState(false);
|
||||||
|
const [payShare, setPayShare] = useState<ExpenseShare | null>(null);
|
||||||
|
const [payAmount, setPayAmount] = useState<number>(0);
|
||||||
|
const [payNotes, setPayNotes] = useState('');
|
||||||
|
const [isPaying, setIsPaying] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
loadData();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -229,6 +236,36 @@ export const ExtraordinaryAdmin: React.FC = () => {
|
|||||||
} catch(e) { alert("Errore file"); }
|
} catch(e) { alert("Errore file"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// MANUAL PAYMENT HANDLERS
|
||||||
|
const openPayModal = (share: ExpenseShare) => {
|
||||||
|
const remaining = Math.max(0, share.amountDue - share.amountPaid);
|
||||||
|
setPayShare(share);
|
||||||
|
setPayAmount(remaining);
|
||||||
|
setPayNotes('Saldo manuale');
|
||||||
|
setShowPayModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleManualPayment = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!selectedExpense || !payShare) return;
|
||||||
|
|
||||||
|
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);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
alert("Errore registrazione pagamento: " + (e.message || "Errore sconosciuto"));
|
||||||
|
} finally {
|
||||||
|
setIsPaying(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 pb-20 animate-fade-in">
|
<div className="space-y-6 pb-20 animate-fade-in">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
@@ -440,6 +477,7 @@ export const ExtraordinaryAdmin: React.FC = () => {
|
|||||||
<th className="p-3 text-right">Da Pagare</th>
|
<th className="p-3 text-right">Da Pagare</th>
|
||||||
<th className="p-3 text-right">Versato</th>
|
<th className="p-3 text-right">Versato</th>
|
||||||
<th className="p-3 text-center">Stato</th>
|
<th className="p-3 text-center">Stato</th>
|
||||||
|
<th className="p-3 text-center">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y">
|
<tbody className="divide-y">
|
||||||
@@ -458,6 +496,17 @@ export const ExtraordinaryAdmin: React.FC = () => {
|
|||||||
{share.status === 'PAID' ? 'Saldato' : share.status === 'PARTIAL' ? 'Parziale' : 'Insoluto'}
|
{share.status === 'PAID' ? 'Saldato' : share.status === 'PARTIAL' ? 'Parziale' : 'Insoluto'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</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"
|
||||||
|
>
|
||||||
|
Paga
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -468,7 +517,7 @@ export const ExtraordinaryAdmin: React.FC = () => {
|
|||||||
<td className="p-3 text-right text-blue-600">
|
<td className="p-3 text-right text-blue-600">
|
||||||
€ {selectedExpense.shares?.reduce((a,b) => a + b.amountPaid, 0).toLocaleString()}
|
€ {selectedExpense.shares?.reduce((a,b) => a + b.amountPaid, 0).toLocaleString()}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td colSpan={2}></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
@@ -478,6 +527,57 @@ export const ExtraordinaryAdmin: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* MANUAL PAYMENT 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>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Importo Incassato (€)</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Banknote className="absolute left-3 top-2.5 w-4 h-4 text-slate-400"/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
className="w-full border p-2 pl-9 rounded-lg text-slate-700 font-bold"
|
||||||
|
value={payAmount}
|
||||||
|
onChange={e => setPayAmount(parseFloat(e.target.value))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Note (Opzionale)</label>
|
||||||
|
<input
|
||||||
|
className="w-full border p-2 rounded-lg text-slate-700 text-sm"
|
||||||
|
placeholder="Es. Bonifico, Contanti..."
|
||||||
|
value={payNotes}
|
||||||
|
onChange={e => setPayNotes(e.target.value)}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user