Files
Condopay/pages/ExtraordinaryUser.tsx
2025-12-09 23:39:48 +01:00

174 lines
10 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { CondoService } from '../services/mockDb';
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";
import { Briefcase, Calendar, CheckCircle2, AlertCircle } from 'lucide-react';
export const ExtraordinaryUser: React.FC = () => {
const [expenses, setExpenses] = useState<any[]>([]); // Using any for composite object from specific API
const [loading, setLoading] = useState(true);
const [paypalClientId, setPaypalClientId] = useState<string>('');
const [successMsg, setSuccessMsg] = useState('');
const [hasFamily, setHasFamily] = useState(true);
useEffect(() => {
const load = async () => {
try {
const user = CondoService.getCurrentUser();
if (!user?.familyId) {
setHasFamily(false);
setLoading(false);
return;
}
const [myExp, condo] = await Promise.all([
CondoService.getMyExpenses(),
CondoService.getActiveCondo()
]);
setExpenses(myExp);
if (condo?.paypalClientId) setPaypalClientId(condo.paypalClientId);
// Update "Last Viewed" timestamp to clear notification
localStorage.setItem('lastViewedExpensesTime', Date.now().toString());
// Trigger event to update Sidebar immediately
window.dispatchEvent(new Event('expenses-viewed'));
} catch(e) { console.error(e); }
finally { setLoading(false); }
};
load();
}, []);
const handlePaymentSuccess = async (expenseId: string, amount: number) => {
try {
await CondoService.payExpense(expenseId, amount);
setSuccessMsg('Pagamento registrato con successo!');
setTimeout(() => setSuccessMsg(''), 3000);
// Refresh
const updated = await CondoService.getMyExpenses();
setExpenses(updated);
} catch(e) { alert("Errore registrazione pagamento"); }
};
if (loading) return <div className="p-8 text-center text-slate-400">Caricamento spese...</div>;
if (!hasFamily) {
return (
<div className="p-8 text-center bg-amber-50 rounded-xl border border-amber-200 text-amber-800">
<AlertCircle className="w-12 h-12 mx-auto mb-3 text-amber-500" />
<h3 className="text-lg font-bold mb-2">Nessuna Famiglia Associata</h3>
<p>Il tuo utente (probabilmente Admin) non è collegato ad alcuna famiglia, quindi non ci sono spese personali da mostrare.</p>
<p className="text-sm mt-2 text-amber-700">Vai in Impostazioni &gt; Utenti e associa il tuo account a una famiglia per testare questa vista.</p>
</div>
);
}
return (
<div className="space-y-6 pb-20 animate-fade-in">
<div>
<h2 className="text-2xl font-bold text-slate-800">Le Mie Spese Extra</h2>
<p className="text-slate-500 text-sm">Lavori straordinari e ripartizioni</p>
</div>
{successMsg && (
<div className="bg-green-100 text-green-700 p-4 rounded-xl flex items-center gap-2 mb-4">
<CheckCircle2 className="w-5 h-5"/> {successMsg}
</div>
)}
{expenses.length === 0 ? (
<div className="text-center p-12 bg-white rounded-xl border border-dashed border-slate-300">
<Briefcase className="w-12 h-12 text-slate-300 mx-auto mb-3"/>
<p className="text-slate-500">Nessuna spesa straordinaria attiva.</p>
</div>
) : (
<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 (
<div key={exp.id} className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden flex flex-col">
<div className="p-5 border-b border-slate-100 bg-slate-50/50 flex justify-between items-start">
<div>
<h3 className="font-bold text-lg text-slate-800">{exp.title}</h3>
<p className="text-xs text-slate-500 mt-1 flex items-center gap-1">
<Calendar className="w-3 h-3"/> {new Date(exp.startDate).toLocaleDateString()}
</p>
</div>
<div className={`px-3 py-1 rounded-full text-xs font-bold uppercase ${
isPaid ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'
}`}>
{isPaid ? 'Saldato' : 'In Sospeso'}
</div>
</div>
<div className="p-5 flex-1 space-y-4">
<div className="flex justify-between items-center text-sm">
<span className="text-slate-500">Quota Totale (100%)</span>
<span className="font-medium text-slate-800"> {exp.totalAmount.toLocaleString()}</span>
</div>
<div className="p-3 bg-blue-50 rounded-lg border border-blue-100">
<div className="flex justify-between items-center text-sm mb-1">
<span className="text-blue-800 font-medium">La tua quota ({exp.myShare.percentage}%)</span>
<span className="font-bold text-blue-900"> {exp.myShare.amountDue.toFixed(2)}</span>
</div>
<div className="w-full bg-blue-200 h-1.5 rounded-full overflow-hidden">
<div
className="bg-blue-600 h-full transition-all duration-500"
style={{ width: `${Math.min(100, (exp.myShare.amountPaid / exp.myShare.amountDue) * 100)}%` }}
/>
</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>
</div>
</div>
</div>
<div className="p-5 border-t border-slate-100 bg-slate-50">
{!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>
</div>
);
})}
</div>
)}
</div>
);
};