import React, { useEffect, useState, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { CondoService } from '../services/mockDb'; import { Family, Payment, AppSettings, MonthStatus, PaymentStatus, Condo } from '../types'; import { ArrowLeft, CheckCircle2, AlertCircle, Plus, Calendar, CreditCard, TrendingUp, Euro, Building as BuildingIcon } from 'lucide-react'; import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js"; const MONTH_NAMES = [ "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre" ]; export const FamilyDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const currentUser = CondoService.getCurrentUser(); const isPrivileged = currentUser?.role === 'admin' || currentUser?.role === 'poweruser'; const [family, setFamily] = useState(null); const [payments, setPayments] = useState([]); const [settings, setSettings] = useState(null); const [condo, setCondo] = useState(undefined); const [loading, setLoading] = useState(true); const [selectedYear, setSelectedYear] = useState(new Date().getFullYear()); const [availableYears, setAvailableYears] = useState([]); const [showAddModal, setShowAddModal] = useState(false); const [newPaymentMonth, setNewPaymentMonth] = useState(new Date().getMonth() + 1); const [newPaymentAmount, setNewPaymentAmount] = useState(0); const [isSubmitting, setIsSubmitting] = useState(false); // Payment Method Selection const [paymentMethod, setPaymentMethod] = useState<'manual' | 'paypal'>('manual'); const [paypalSuccessMsg, setPaypalSuccessMsg] = useState(''); useEffect(() => { if (!id) return; const loadData = async () => { setLoading(true); try { const [famList, famPayments, appSettings, years, activeCondo] = await Promise.all([ CondoService.getFamilies(), CondoService.getPaymentsByFamily(id), CondoService.getSettings(), CondoService.getAvailableYears(), CondoService.getActiveCondo() ]); const foundFamily = famList.find(f => f.id === id); if (foundFamily) { setFamily(foundFamily); setPayments(famPayments); setSettings(appSettings); setCondo(activeCondo); // Use Family Custom Quota OR Condo Default const defaultAmount = foundFamily.customMonthlyQuota ?? activeCondo?.defaultMonthlyQuota ?? 100; setNewPaymentAmount(defaultAmount); setAvailableYears(years); setSelectedYear(appSettings.currentYear); } else { navigate('/'); } } catch (e) { console.error("Error loading family details", e); } finally { setLoading(false); } }; loadData(); }, [id, navigate]); const monthlyStatus: MonthStatus[] = useMemo(() => { const now = new Date(); const currentRealYear = now.getFullYear(); const currentRealMonth = now.getMonth() + 1; // 1-12 const currentDay = now.getDate(); // Default due day is 10 if not set const dueDay = condo?.dueDay || 10; return Array.from({ length: 12 }, (_, i) => { const monthNum = i + 1; const payment = payments.find(p => p.forMonth === monthNum && p.forYear === selectedYear); let status = PaymentStatus.UPCOMING; if (payment) { status = PaymentStatus.PAID; } else { if (selectedYear < currentRealYear) { status = PaymentStatus.UNPAID; // Past year unpaid } else if (selectedYear === currentRealYear) { if (monthNum < currentRealMonth) { // Past month in current year -> Unpaid status = PaymentStatus.UNPAID; } else if (monthNum === currentRealMonth) { // Current month if (currentDay > dueDay) { status = PaymentStatus.UNPAID; // Passed due date } else if (currentDay >= (dueDay - 10)) { status = PaymentStatus.PENDING; // Within 10 days before due } else { status = PaymentStatus.UPCOMING; // Early in the month } } else { status = PaymentStatus.UPCOMING; // Future month } } else { status = PaymentStatus.UPCOMING; // Future year } } return { monthIndex: i, status, payment }; }); }, [payments, selectedYear, condo]); const chartData = useMemo(() => { return monthlyStatus.map(m => ({ label: MONTH_NAMES[m.monthIndex].substring(0, 3), fullLabel: MONTH_NAMES[m.monthIndex], amount: m.payment ? m.payment.amount : 0, isPaid: m.status === PaymentStatus.PAID, isFuture: m.status === PaymentStatus.UPCOMING })); }, [monthlyStatus]); const maxChartValue = useMemo(() => { const max = Math.max(...chartData.map(d => d.amount)); // Check family specific quota first const baseline = family?.customMonthlyQuota ?? condo?.defaultMonthlyQuota ?? 100; return max > 0 ? Math.max(max * 1.2, baseline) : baseline; }, [chartData, condo, family]); const handlePaymentSuccess = async (details?: any) => { if (!family || !id) return; setIsSubmitting(true); try { // Format date to SQL compatible string: YYYY-MM-DD HH:mm:ss // This is safe for both MySQL (Strict Mode) and PostgreSQL const now = new Date(); const sqlDate = now.toISOString().slice(0, 19).replace('T', ' '); const payment = await CondoService.addPayment({ familyId: id, amount: newPaymentAmount, forMonth: newPaymentMonth, forYear: selectedYear, datePaid: sqlDate, notes: details ? `Pagato con PayPal (ID: ${details.id})` : '' }); setPayments([...payments, payment]); if (!availableYears.includes(selectedYear)) { setAvailableYears([...availableYears, selectedYear].sort((a,b) => b-a)); } if (details) { setPaypalSuccessMsg("Pagamento riuscito!"); setTimeout(() => { setShowAddModal(false); setPaypalSuccessMsg(""); }, 2000); } else { setShowAddModal(false); } } catch (e: any) { console.error("Failed to add payment", e); // Clean error message if it's a JSON string from backend let msg = e.message || "Errore sconosciuto"; try { const parsed = JSON.parse(msg); if (parsed.error && parsed.error.includes("Incorrect datetime")) { msg = "Errore data sistema (DB strict mode). Contatta l'assistenza."; } else if (parsed.error) { msg = parsed.error; } } catch {} alert(`Errore durante il salvataggio: ${msg}`); } finally { setIsSubmitting(false); } }; const handleManualSubmit = (e: React.FormEvent) => { e.preventDefault(); handlePaymentSuccess(); }; const isPayPalEnabled = condo?.paypalClientId && settings?.features.payPal; const handleOpenAddModal = (monthIndex?: number) => { if (monthIndex !== undefined) { setNewPaymentMonth(monthIndex + 1); } // LOGIC: // Admin -> Defaults to Manual (can switch if PayPal enabled) // User -> Defaults to PayPal (cannot switch to manual) if (isPrivileged) { setPaymentMethod('manual'); } else { setPaymentMethod('paypal'); } setShowAddModal(true); }; if (loading) return
Caricamento dettagli...
; if (!family) return
Famiglia non trovata.
; return (
{/* Header Responsive */}

{family.name}

Interno: {family.unitNumber} {family.customMonthlyQuota && ( Quota Personalizzata: €{family.customMonthlyQuota} )}
{/* Stats Summary - Stack on mobile */}

Mesi Saldati

{monthlyStatus.filter(m => m.status === PaymentStatus.PAID).length} / 12

Mesi Insoluti

{monthlyStatus.filter(m => m.status === PaymentStatus.UNPAID).length}

Totale Versato

€ {payments.filter(p => p.forYear === selectedYear).reduce((acc, curr) => acc + curr.amount, 0).toLocaleString()}

{/* Monthly Grid */}

Dettaglio {selectedYear} (Scadenza: Giorno {condo?.dueDay || 10})

{monthlyStatus.map((month) => (
{MONTH_NAMES[month.monthIndex]} {month.status === PaymentStatus.PAID && ( Saldato )} {month.status === PaymentStatus.UNPAID && ( Insoluto )} {month.status === PaymentStatus.PENDING && ( In Scadenza )} {month.status === PaymentStatus.UPCOMING && ( Futuro )}
{month.payment ? (

Importo: € {month.payment.amount}

{new Date(month.payment.datePaid).toLocaleDateString()}

) : (

Nessun pagamento

{(month.status === PaymentStatus.UNPAID || month.status === PaymentStatus.PENDING) && ( )}
)}
))}
{/* Payment Trend Chart (Scrollable) */}

Andamento

Versato
Mancante
{chartData.map((data, index) => { const heightPercentage = Math.max((data.amount / maxChartValue) * 100, 4); return (
{data.amount > 0 && (
)} {data.amount === 0 && !data.isFuture && (
)}
0 ? 'text-slate-700' : 'text-slate-400' }`}> {data.label}
); })}
{/* Add Payment Modal */} {showAddModal && (

Registra Pagamento

{/* Payment Method Switcher - ONLY FOR PRIVILEGED USERS */} {isPayPalEnabled && isPrivileged && (
)} {/* Non-privileged users (regular tenants) without PayPal enabled: Show Block Message */} {!isPrivileged && !isPayPalEnabled ? (

Pagamenti Online non attivi

Contatta l'amministratore per saldare la tua rata in contanti o bonifico.

) : ( <> {paymentMethod === 'manual' && isPrivileged ? (
setNewPaymentAmount(parseFloat(e.target.value))} className="w-full border border-slate-300 rounded-xl p-3 pl-10 focus:ring-2 focus:ring-blue-500 outline-none text-lg font-medium" />
) : (

Stai per pagare

€ {newPaymentAmount.toFixed(2)}

{MONTH_NAMES[newPaymentMonth - 1]} {selectedYear}

{paypalSuccessMsg ? (
{paypalSuccessMsg}
) : (
{isPayPalEnabled && ( { return actions.order.create({ intent: "CAPTURE", purchase_units: [ { description: `Quota ${MONTH_NAMES[newPaymentMonth - 1]} ${selectedYear} - Famiglia ${family.name}`, amount: { currency_code: "EUR", value: newPaymentAmount.toFixed(2), }, }, ], }); }} onApprove={async (data, actions) => { if(!actions.order) return; try { const details = await actions.order.capture(); await handlePaymentSuccess(details); } catch (err) { console.error("PayPal Capture Error", err); alert("Errore durante il completamento del pagamento PayPal."); } }} onError={(err) => { console.error("PayPal Button Error", err); alert("Errore caricamento PayPal: " + err.toString()); }} /> )}
)}

Il pagamento sarà registrato automaticamente.

)} )}
)}
); };