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, History } from 'lucide-react'; export const ExtraordinaryAdmin: React.FC = () => { const [expenses, setExpenses] = useState([]); const [families, setFamilies] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [showDetailsModal, setShowDetailsModal] = useState(false); const [selectedExpense, setSelectedExpense] = useState(null); const [isEditing, setIsEditing] = useState(false); const [editingId, setEditingId] = useState(null); // Form State const [formTitle, setFormTitle] = useState(''); const [formDesc, setFormDesc] = useState(''); const [formStart, setFormStart] = useState(''); const [formEnd, setFormEnd] = useState(''); const [formContractor, setFormContractor] = useState(''); const [formItems, setFormItems] = useState([{ description: '', amount: 0 }]); const [formShares, setFormShares] = useState([]); // Working state for shares const [formAttachments, setFormAttachments] = useState<{fileName: string, fileType: string, data: string}[]>([]); const [selectedFamilyIds, setSelectedFamilyIds] = useState([]); // Helper to track checkboxes // Manual Payment Modal State const [showPayModal, setShowPayModal] = useState(false); const [payShare, setPayShare] = useState(null); const [payAmount, setPayAmount] = useState(0); const [payNotes, setPayNotes] = useState(''); const [isPaying, setIsPaying] = useState(false); const [paymentHistory, setPaymentHistory] = useState([]); const [loadingHistory, setLoadingHistory] = useState(false); useEffect(() => { loadData(); }, []); const loadData = async () => { setLoading(true); try { const [expList, famList] = await Promise.all([ CondoService.getExpenses(), CondoService.getFamilies() ]); setExpenses(expList); setFamilies(famList); } catch(e) { console.error(e); } finally { setLoading(false); } }; // Calculation Helpers const totalAmount = formItems.reduce((acc, item) => acc + (item.amount || 0), 0); const recalculateShares = (selectedIds: string[], manualMode = false) => { if (manualMode) return; const count = selectedIds.length; if (count === 0) { setFormShares([]); return; } const percentage = 100 / count; const newShares: ExpenseShare[] = selectedIds.map(fid => { // Preserve existing data if available const existing = formShares.find(s => s.familyId === fid); if (existing) { return { ...existing, percentage: parseFloat(percentage.toFixed(2)), amountDue: parseFloat(((totalAmount * percentage) / 100).toFixed(2)) }; } return { familyId: fid, percentage: parseFloat(percentage.toFixed(2)), amountDue: parseFloat(((totalAmount * percentage) / 100).toFixed(2)), amountPaid: 0, status: 'UNPAID' }; }); // Adjust rounding error on last item if (newShares.length > 0) { const sumDue = newShares.reduce((a, b) => a + b.amountDue, 0); const diff = totalAmount - sumDue; if (diff !== 0) { newShares[newShares.length - 1].amountDue += diff; } } setFormShares(newShares); }; const handleFamilyToggle = (familyId: string) => { const newSelected = selectedFamilyIds.includes(familyId) ? selectedFamilyIds.filter(id => id !== familyId) : [...selectedFamilyIds, familyId]; setSelectedFamilyIds(newSelected); recalculateShares(newSelected); }; const handleShareChange = (index: number, field: 'percentage', value: number) => { const newShares = [...formShares]; newShares[index].percentage = value; newShares[index].amountDue = (totalAmount * value) / 100; setFormShares(newShares); }; const handleItemChange = (index: number, field: keyof ExpenseItem, value: any) => { const newItems = [...formItems]; // @ts-ignore newItems[index][field] = value; setFormItems(newItems); }; useEffect(() => { if (selectedFamilyIds.length > 0) { recalculateShares(selectedFamilyIds); } }, [totalAmount]); const handleFileChange = async (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const newAtts = []; for(let i=0; i((resolve) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as string); reader.readAsDataURL(file); }); newAtts.push({ fileName: file.name, fileType: file.type, data: base64 }); } setFormAttachments([...formAttachments, ...newAtts]); } }; const openCreateModal = () => { setIsEditing(false); setEditingId(null); setFormTitle(''); setFormDesc(''); setFormStart(''); setFormEnd(''); setFormContractor(''); setFormItems([{description:'', amount:0}]); setFormShares([]); setFormAttachments([]); setSelectedFamilyIds([]); setShowModal(true); }; const openEditModal = async (exp: ExtraordinaryExpense) => { try { const detail = await CondoService.getExpenseDetails(exp.id); setIsEditing(true); setEditingId(exp.id); setFormTitle(detail.title); setFormDesc(detail.description); setFormStart(detail.startDate ? new Date(detail.startDate).toISOString().split('T')[0] : ''); setFormEnd(detail.endDate ? new Date(detail.endDate).toISOString().split('T')[0] : ''); setFormContractor(detail.contractorName); setFormItems(detail.items || []); const currentShares = detail.shares || []; setFormShares(currentShares); setSelectedFamilyIds(currentShares.map(s => s.familyId)); setFormAttachments([]); setShowModal(true); } catch(e) { alert("Errore caricamento dettagli"); } }; const handleDeleteExpense = async (id: string) => { if (!confirm("Sei sicuro di voler eliminare questo progetto? Questa azione è irreversibile e cancellerà anche lo storico dei pagamenti associati.")) return; try { await CondoService.deleteExpense(id); loadData(); } catch (e) { alert("Errore eliminazione progetto"); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { if (isEditing && editingId) { await CondoService.updateExpense(editingId, { title: formTitle, description: formDesc, startDate: formStart, endDate: formEnd, contractorName: formContractor, items: formItems, shares: formShares }); } else { await CondoService.createExpense({ title: formTitle, description: formDesc, startDate: formStart, endDate: formEnd, contractorName: formContractor, items: formItems, shares: formShares, attachments: formAttachments }); } setShowModal(false); loadData(); } catch(e) { alert('Errore salvataggio'); } }; const openDetails = async (expense: ExtraordinaryExpense) => { const fullDetails = await CondoService.getExpenseDetails(expense.id); setSelectedExpense(fullDetails); setShowDetailsModal(true); }; const openAttachment = async (expenseId: string, attId: string) => { try { const file = await CondoService.getExpenseAttachment(expenseId, attId); const win = window.open(); if (win) { if (file.fileType.startsWith('image/') || file.fileType === 'application/pdf') { win.document.write(``); } else { win.document.write(`Download ${file.fileName}`); } } } catch(e) { alert("Errore file"); } }; // MANUAL PAYMENT HANDLERS 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) => { e.preventDefault(); if (!selectedExpense || !payShare) return; setIsPaying(true); try { await CondoService.payExpense(selectedExpense.id, payAmount, payShare.familyId); await refreshShareData(); // Reset input setPayAmount(0); setPayNotes(''); } catch (e: any) { 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 (

Spese Straordinarie

Gestione lavori e appalti

{/* List */}
{expenses.map(exp => (
Lavori € {exp.totalAmount.toLocaleString()}

{exp.title}

{exp.description}

{exp.contractorName}
{new Date(exp.startDate).toLocaleDateString()}
))}
{/* CREATE/EDIT MODAL */} {showModal && (

{isEditing ? 'Modifica Progetto' : 'Crea Progetto Straordinario'}

{/* General Info */}
setFormTitle(e.target.value)} required /> setFormContractor(e.target.value)} required />