Update ExtraordinaryUser.tsx

This commit is contained in:
2025-12-11 00:15:10 +01:00
committed by GitHub
parent 32adfbbc58
commit 1f46817c10

View File

@@ -2,7 +2,7 @@
import React, { useEffect, useState } from 'react';
import { CondoService } from '../services/mockDb';
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";
import { Briefcase, Calendar, CheckCircle2, AlertCircle, Eye, Paperclip, X, FileText, Download } from 'lucide-react';
import { Briefcase, Calendar, CheckCircle2, AlertCircle, Eye, Paperclip, X, FileText, Download, Users, Euro } from 'lucide-react';
import { ExtraordinaryExpense } from '../types';
export const ExtraordinaryUser: React.FC = () => {
@@ -11,6 +11,7 @@ export const ExtraordinaryUser: React.FC = () => {
const [paypalClientId, setPaypalClientId] = useState<string>('');
const [successMsg, setSuccessMsg] = useState('');
const [hasFamily, setHasFamily] = useState(true);
const [userFamilyId, setUserFamilyId] = useState<string | null>(null);
// Details Modal State
const [showDetails, setShowDetails] = useState(false);
@@ -26,6 +27,7 @@ export const ExtraordinaryUser: React.FC = () => {
setLoading(false);
return;
}
setUserFamilyId(user.familyId);
const [myExp, condo] = await Promise.all([
CondoService.getMyExpenses(),
@@ -50,9 +52,14 @@ export const ExtraordinaryUser: React.FC = () => {
await CondoService.payExpense(expenseId, amount);
setSuccessMsg('Pagamento registrato con successo!');
setTimeout(() => setSuccessMsg(''), 3000);
// Refresh
const updated = await CondoService.getMyExpenses();
setExpenses(updated);
// Refresh logic
const updatedList = await CondoService.getMyExpenses();
setExpenses(updatedList);
// Also update selected expense if modal is open
if (selectedExpense) {
const updatedDetails = await CondoService.getExpenseDetails(expenseId);
setSelectedExpense(updatedDetails);
}
} catch(e) { alert("Errore registrazione pagamento"); }
};
@@ -119,7 +126,6 @@ export const ExtraordinaryUser: React.FC = () => {
) : (
<div className="grid gap-6 md:grid-cols-2">
{expenses.map(exp => {
const remaining = exp.myShare.amountDue - exp.myShare.amountPaid;
const isPaid = exp.myShare.status === 'PAID';
return (
@@ -162,47 +168,13 @@ export const ExtraordinaryUser: React.FC = () => {
</div>
<div className="flex justify-between text-xs mt-1 text-blue-600">
<span>Versato: {exp.myShare.amountPaid.toFixed(2)}</span>
<span>Restante: {Math.max(0, remaining).toFixed(2)}</span>
<span>Restante: {(exp.myShare.amountDue - exp.myShare.amountPaid).toFixed(2)}</span>
</div>
</div>
</div>
<div className="p-5 border-t border-slate-100 bg-slate-50" onClick={(e) => e.stopPropagation()}>
{!isPaid && remaining > 0.01 && (
<>
{paypalClientId ? (
<PayPalScriptProvider options={{ clientId: paypalClientId, currency: "EUR" }}>
<PayPalButtons
style={{ layout: "horizontal", height: 40 }}
createOrder={(data, actions) => {
return actions.order.create({
intent: "CAPTURE",
purchase_units: [{
description: `Spesa Straordinaria: ${exp.title}`,
amount: { currency_code: "EUR", value: remaining.toFixed(2) }
}]
});
}}
onApprove={(data, actions) => {
if(!actions.order) return Promise.resolve();
return actions.order.capture().then(() => {
handlePaymentSuccess(exp.id, remaining);
});
}}
/>
</PayPalScriptProvider>
) : (
<div className="text-center text-xs text-red-500 bg-red-50 p-2 rounded">
Pagamenti online non configurati. Contatta l'amministratore.
</div>
)}
</>
)}
{isPaid && (
<div className="text-center text-green-600 font-bold text-sm flex items-center justify-center gap-2">
<CheckCircle2 className="w-5 h-5"/> Nessun importo dovuto
</div>
)}
<div className="p-4 border-t border-slate-100 bg-slate-50 text-center text-xs text-blue-600 font-medium">
Clicca per dettagli e pagamenti
</div>
</div>
);
@@ -212,8 +184,8 @@ export const ExtraordinaryUser: React.FC = () => {
{/* DETAILS MODAL */}
{showDetails && selectedExpense && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm animate-in fade-in">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] flex flex-col overflow-hidden">
<div className="fixed inset-0 bg-black/50 z-[100] flex items-center justify-center p-4 backdrop-blur-sm animate-in fade-in">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-3xl max-h-[90vh] flex flex-col overflow-hidden">
<div className="flex justify-between items-center p-5 border-b border-slate-100 bg-slate-50">
<div>
<h3 className="font-bold text-xl text-slate-800">{selectedExpense.title}</h3>
@@ -224,51 +196,88 @@ export const ExtraordinaryUser: React.FC = () => {
</button>
</div>
<div className="p-6 overflow-y-auto space-y-6">
{/* Description */}
<div>
<h4 className="text-sm font-bold text-slate-700 mb-2 flex items-center gap-2"><FileText className="w-4 h-4"/> Descrizione Lavori</h4>
<p className="text-slate-600 text-sm leading-relaxed bg-slate-50 p-4 rounded-lg border border-slate-100">
{selectedExpense.description}
</p>
</div>
{/* Dates & Totals */}
<div className="grid grid-cols-2 gap-4">
<div className="bg-blue-50 p-3 rounded-lg">
<p className="text-xs text-blue-600 font-bold uppercase mb-1">Inizio Lavori</p>
<p className="text-sm font-medium text-slate-700">
{new Date(selectedExpense.startDate).toLocaleDateString()}
<div className="p-6 overflow-y-auto space-y-8">
{/* Top Section: Info & Dates */}
<div className="space-y-4">
<div>
<h4 className="text-sm font-bold text-slate-700 mb-2 flex items-center gap-2"><FileText className="w-4 h-4"/> Descrizione Lavori</h4>
<p className="text-slate-600 text-sm leading-relaxed bg-slate-50 p-4 rounded-lg border border-slate-100">
{selectedExpense.description}
</p>
</div>
<div className="bg-green-50 p-3 rounded-lg">
<p className="text-xs text-green-600 font-bold uppercase mb-1">Totale Progetto</p>
<p className="text-sm font-medium text-slate-700">
{selectedExpense.totalAmount.toLocaleString()}
</p>
<div className="grid grid-cols-2 gap-4">
<div className="bg-blue-50 p-3 rounded-lg border border-blue-100">
<p className="text-xs text-blue-600 font-bold uppercase mb-1">Inizio Lavori</p>
<p className="text-sm font-medium text-slate-700">
{new Date(selectedExpense.startDate).toLocaleDateString()}
</p>
</div>
<div className="bg-green-50 p-3 rounded-lg border border-green-100">
<p className="text-xs text-green-600 font-bold uppercase mb-1">Costo Totale Progetto</p>
<p className="text-sm font-medium text-slate-700">
{selectedExpense.totalAmount.toLocaleString()}
</p>
</div>
</div>
</div>
{/* Items List */}
<div>
<h4 className="text-sm font-bold text-slate-700 mb-2">Dettaglio Voci di Spesa</h4>
<div className="border rounded-lg overflow-hidden">
<table className="w-full text-sm text-left">
<thead className="bg-slate-50 text-slate-600">
<tr>
<th className="p-3 font-semibold">Descrizione</th>
<th className="p-3 font-semibold text-right">Importo</th>
</tr>
</thead>
<tbody className="divide-y">
{selectedExpense.items.map((item, i) => (
<tr key={i}>
<td className="p-3 text-slate-700">{item.description}</td>
<td className="p-3 text-right text-slate-900 font-medium"> {item.amount.toLocaleString()}</td>
{/* Middle Section: Items and Distribution Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Items List */}
<div>
<h4 className="text-sm font-bold text-slate-700 mb-2 border-b pb-1">Voci di Spesa (Totale)</h4>
<div className="bg-slate-50 rounded-lg overflow-hidden border border-slate-200">
<table className="w-full text-sm text-left">
<tbody className="divide-y divide-slate-200">
{selectedExpense.items.map((item, i) => (
<tr key={i}>
<td className="p-3 text-slate-600 text-xs">{item.description}</td>
<td className="p-3 text-right text-slate-900 font-medium text-xs"> {item.amount.toLocaleString()}</td>
</tr>
))}
<tr className="bg-slate-100 font-bold">
<td className="p-3 text-xs">Totale</td>
<td className="p-3 text-right text-xs"> {selectedExpense.totalAmount.toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
</tbody>
</table>
</div>
</div>
{/* Shares Table (Piano di Ripartizione) */}
<div>
<h4 className="text-sm font-bold text-slate-700 mb-2 border-b pb-1 flex items-center gap-2">
<Users className="w-4 h-4"/> Piano di Ripartizione
</h4>
<div className="bg-white rounded-lg overflow-hidden border border-slate-200 h-full max-h-60 overflow-y-auto">
<table className="w-full text-xs text-left">
<thead className="bg-slate-50 text-slate-500 font-semibold sticky top-0">
<tr>
<th className="p-2">Condomino</th>
<th className="p-2 text-right">%</th>
<th className="p-2 text-right">Quota</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{selectedExpense.shares?.map((share, i) => {
const isMe = share.familyId === userFamilyId;
return (
<tr key={i} className={isMe ? "bg-blue-50" : ""}>
<td className="p-2 font-medium text-slate-700 truncate max-w-[100px]">
{share.familyName}
{isMe && <span className="ml-1 text-[9px] bg-blue-100 text-blue-700 px-1 rounded">TU</span>}
</td>
<td className="p-2 text-right text-slate-500">{share.percentage}%</td>
<td className={`p-2 text-right font-bold ${isMe ? 'text-blue-700' : 'text-slate-700'}`}>
{share.amountDue.toFixed(2)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
@@ -297,10 +306,60 @@ export const ExtraordinaryUser: React.FC = () => {
)}
</div>
<div className="p-4 border-t border-slate-100 bg-slate-50 flex justify-end">
<button onClick={() => setShowDetails(false)} className="px-6 py-2 bg-slate-800 text-white rounded-lg hover:bg-slate-900 transition-colors font-medium text-sm">
Chiudi
</button>
{/* Payment Footer - Only visible here now */}
<div className="p-4 border-t border-slate-100 bg-slate-50">
{(() => {
const myShare = selectedExpense.shares?.find(s => s.familyId === userFamilyId);
if (!myShare) return null;
const remaining = Math.max(0, myShare.amountDue - myShare.amountPaid);
if (remaining < 0.01) {
return (
<div className="flex justify-end items-center gap-4">
<span className="text-green-600 font-bold flex items-center gap-2 text-sm"><CheckCircle2 className="w-5 h-5"/> Quota Saldata</span>
<button onClick={() => setShowDetails(false)} className="px-6 py-2 bg-slate-200 text-slate-700 rounded-lg hover:bg-slate-300 font-medium text-sm">Chiudi</button>
</div>
);
}
return (
<div className="space-y-4">
<div className="flex justify-between items-center text-sm">
<span className="font-bold text-slate-700">Da Saldare:</span>
<span className="font-bold text-red-600 text-lg"> {remaining.toFixed(2)}</span>
</div>
{paypalClientId ? (
<PayPalScriptProvider options={{ clientId: paypalClientId, currency: "EUR" }}>
<PayPalButtons
style={{ layout: "horizontal", height: 45 }}
createOrder={(data, actions) => {
return actions.order.create({
intent: "CAPTURE",
purchase_units: [{
description: `Spesa Straordinaria: ${selectedExpense.title}`,
amount: { currency_code: "EUR", value: remaining.toFixed(2) }
}]
});
}}
onApprove={(data, actions) => {
if(!actions.order) return Promise.resolve();
return actions.order.capture().then(() => {
handlePaymentSuccess(selectedExpense.id, remaining);
});
}}
/>
</PayPalScriptProvider>
) : (
<div className="bg-red-50 text-red-600 p-2 rounded text-center text-xs">
Pagamenti online non configurati.
</div>
)}
<div className="flex justify-end">
<button onClick={() => setShowDetails(false)} className="w-full sm:w-auto px-6 py-2 border border-slate-300 text-slate-600 rounded-lg hover:bg-slate-100 font-medium text-sm">Chiudi</button>
</div>
</div>
);
})()}
</div>
</div>
</div>