484 lines
29 KiB
TypeScript
484 lines
29 KiB
TypeScript
|
|
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 } from 'lucide-react';
|
|
|
|
export const ExtraordinaryAdmin: React.FC = () => {
|
|
const [expenses, setExpenses] = useState<ExtraordinaryExpense[]>([]);
|
|
const [families, setFamilies] = useState<Family[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [showModal, setShowModal] = useState(false);
|
|
const [showDetailsModal, setShowDetailsModal] = useState(false);
|
|
const [selectedExpense, setSelectedExpense] = useState<ExtraordinaryExpense | null>(null);
|
|
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [editingId, setEditingId] = useState<string | null>(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<ExpenseItem[]>([{ description: '', amount: 0 }]);
|
|
const [formShares, setFormShares] = useState<ExpenseShare[]>([]); // Working state for shares
|
|
const [formAttachments, setFormAttachments] = useState<{fileName: string, fileType: string, data: string}[]>([]);
|
|
const [selectedFamilyIds, setSelectedFamilyIds] = useState<string[]>([]); // Helper to track checkboxes
|
|
|
|
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) {
|
|
// If editing and we just toggled someone else, re-calc percentages evenly?
|
|
// Or keep manual adjustments?
|
|
// For simplicity: auto-recalc resets percentages evenly.
|
|
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);
|
|
};
|
|
|
|
// Trigger share recalc when total changes (if not manual)
|
|
// We only trigger auto-recalc if not editing existing complex shares,
|
|
// OR if editing but user hasn't manually messed with them yet (simplification: always recalc on total change for now)
|
|
useEffect(() => {
|
|
if (selectedFamilyIds.length > 0) {
|
|
recalculateShares(selectedFamilyIds);
|
|
}
|
|
}, [totalAmount]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (e.target.files && e.target.files.length > 0) {
|
|
const newAtts = [];
|
|
for(let i=0; i<e.target.files.length; i++) {
|
|
const file = e.target.files[i];
|
|
const base64 = await new Promise<string>((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) => {
|
|
// Fetch full details first to get items and shares
|
|
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 || []);
|
|
|
|
// Populate shares for editing
|
|
const currentShares = detail.shares || [];
|
|
setFormShares(currentShares);
|
|
setSelectedFamilyIds(currentShares.map(s => s.familyId));
|
|
|
|
// Attachments (Cannot edit attachments in this simple view for now, cleared)
|
|
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 // Now we send shares to sync
|
|
});
|
|
} 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(`<iframe src="${file.data}" frameborder="0" style="border:0; top:0px; left:0px; bottom:0px; right:0px; width:100%; height:100%;" allowfullscreen></iframe>`);
|
|
} else {
|
|
win.document.write(`<a href="${file.data}" download="${file.fileName}">Download ${file.fileName}</a>`);
|
|
}
|
|
}
|
|
} catch(e) { alert("Errore file"); }
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6 pb-20 animate-fade-in">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-slate-800">Spese Straordinarie</h2>
|
|
<p className="text-slate-500 text-sm">Gestione lavori e appalti</p>
|
|
</div>
|
|
<button onClick={openCreateModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex gap-2 hover:bg-blue-700">
|
|
<Plus className="w-5 h-5"/> Nuova Spesa
|
|
</button>
|
|
</div>
|
|
|
|
{/* List */}
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{expenses.map(exp => (
|
|
<div key={exp.id} className="bg-white p-5 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-all relative group">
|
|
<div className="absolute top-4 right-4 flex gap-2">
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); openEditModal(exp); }}
|
|
className="p-2 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded-full transition-colors"
|
|
title="Modifica"
|
|
>
|
|
<Pencil className="w-4 h-4"/>
|
|
</button>
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); handleDeleteExpense(exp.id); }}
|
|
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-full transition-colors"
|
|
title="Elimina"
|
|
>
|
|
<Trash2 className="w-4 h-4"/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-start mb-3">
|
|
<span className="bg-purple-100 text-purple-700 text-xs font-bold px-2 py-1 rounded uppercase">Lavori</span>
|
|
<span className="font-bold text-slate-800 pr-20">€ {exp.totalAmount.toLocaleString()}</span>
|
|
</div>
|
|
<h3 className="font-bold text-lg text-slate-800 mb-1">{exp.title}</h3>
|
|
<p className="text-sm text-slate-500 mb-4 line-clamp-2">{exp.description}</p>
|
|
|
|
<div className="space-y-2 text-sm text-slate-600 mb-4">
|
|
<div className="flex items-center gap-2"><Briefcase className="w-4 h-4 text-slate-400"/> {exp.contractorName}</div>
|
|
<div className="flex items-center gap-2"><Calendar className="w-4 h-4 text-slate-400"/> {new Date(exp.startDate).toLocaleDateString()}</div>
|
|
</div>
|
|
|
|
<button onClick={() => openDetails(exp)} className="w-full py-2 border border-slate-200 rounded-lg text-slate-600 font-medium hover:bg-slate-50 flex items-center justify-center gap-2">
|
|
<Eye className="w-4 h-4"/> Dettagli & Pagamenti
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* CREATE/EDIT MODAL */}
|
|
{showModal && (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-4xl p-6 animate-in fade-in zoom-in duration-200 flex flex-col max-h-[90vh]">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h3 className="font-bold text-xl text-slate-800">{isEditing ? 'Modifica Progetto' : 'Crea Progetto Straordinario'}</h3>
|
|
<button onClick={() => setShowModal(false)}><X className="w-6 h-6 text-slate-400"/></button>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto pr-2">
|
|
<form id="createForm" onSubmit={handleSubmit} className="space-y-6">
|
|
{/* General Info */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<input className="border p-2 rounded" placeholder="Titolo Lavori" value={formTitle} onChange={e => setFormTitle(e.target.value)} required />
|
|
<input className="border p-2 rounded" placeholder="Azienda Appaltatrice" value={formContractor} onChange={e => setFormContractor(e.target.value)} required />
|
|
<div className="col-span-2">
|
|
<textarea className="w-full border p-2 rounded h-20" placeholder="Descrizione..." value={formDesc} onChange={e => setFormDesc(e.target.value)} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-2 col-span-2 md:col-span-1">
|
|
<div><label className="text-xs font-bold text-slate-500">Inizio</label><input type="date" className="w-full border p-2 rounded" value={formStart} onChange={e => setFormStart(e.target.value)} required /></div>
|
|
<div><label className="text-xs font-bold text-slate-500">Fine (Prevista)</label><input type="date" className="w-full border p-2 rounded" value={formEnd} onChange={e => setFormEnd(e.target.value)} /></div>
|
|
</div>
|
|
{!isEditing && (
|
|
<div>
|
|
<label className="text-xs font-bold text-slate-500 block mb-1">Allegati (Preventivi, Contratti)</label>
|
|
<input type="file" multiple onChange={handleFileChange} className="w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Items */}
|
|
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
|
|
<h4 className="font-bold text-slate-700 mb-2 flex justify-between">
|
|
Voci di Spesa
|
|
<span className="text-blue-600">Totale: € {totalAmount.toLocaleString()}</span>
|
|
</h4>
|
|
{formItems.map((item, idx) => (
|
|
<div key={idx} className="flex gap-2 mb-2">
|
|
<input className="flex-1 border p-2 rounded text-sm" placeholder="Descrizione voce..." value={item.description} onChange={e => handleItemChange(idx, 'description', e.target.value)} required />
|
|
<input type="number" className="w-32 border p-2 rounded text-sm" placeholder="Importo" value={item.amount} onChange={e => handleItemChange(idx, 'amount', parseFloat(e.target.value))} required />
|
|
{idx > 0 && <button type="button" onClick={() => setFormItems(formItems.filter((_, i) => i !== idx))} className="text-red-500"><X className="w-4 h-4"/></button>}
|
|
</div>
|
|
))}
|
|
<button type="button" onClick={() => setFormItems([...formItems, {description:'', amount:0}])} className="text-sm text-blue-600 font-medium flex items-center gap-1 mt-2">
|
|
<Plus className="w-3 h-3"/> Aggiungi Voce
|
|
</button>
|
|
</div>
|
|
|
|
{/* Distribution - Visible in BOTH Edit and Create */}
|
|
<div>
|
|
<h4 className="font-bold text-slate-700 mb-2">Ripartizione Famiglie</h4>
|
|
<div className="max-h-60 overflow-y-auto border rounded-lg divide-y">
|
|
{families.map(fam => {
|
|
const share = formShares.find(s => s.familyId === fam.id);
|
|
return (
|
|
<div key={fam.id} className="flex items-center justify-between p-3 hover:bg-slate-50">
|
|
<label className="flex items-center gap-2 cursor-pointer flex-1">
|
|
<input type="checkbox" checked={selectedFamilyIds.includes(fam.id)} onChange={() => handleFamilyToggle(fam.id)} className="rounded text-blue-600"/>
|
|
<span className="text-sm font-medium text-slate-700">{fam.name} <span className="text-slate-400 font-normal">({fam.unitNumber})</span></span>
|
|
</label>
|
|
{share && (
|
|
<div className="flex items-center gap-2">
|
|
<div className="relative">
|
|
<input
|
|
type="number"
|
|
className="w-16 border rounded p-1 text-right text-sm"
|
|
value={share.percentage}
|
|
onChange={e => handleShareChange(formShares.indexOf(share), 'percentage', parseFloat(e.target.value))}
|
|
/>
|
|
<span className="absolute right-6 top-1 text-xs text-slate-400">%</span>
|
|
</div>
|
|
<div className="w-24 text-right text-sm font-bold text-slate-700">€ {share.amountDue.toFixed(2)}</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
{isEditing && (
|
|
<div className="bg-amber-50 p-3 rounded text-amber-800 text-sm border border-amber-200 flex items-start gap-2">
|
|
<CheckCircle2 className="w-5 h-5 flex-shrink-0"/>
|
|
<p>Nota: Modificando le quote, lo stato dei pagamenti verrà aggiornato in base agli importi già versati dalle famiglie.</p>
|
|
</div>
|
|
)}
|
|
</form>
|
|
</div>
|
|
<div className="pt-4 border-t mt-4 flex justify-end gap-2">
|
|
<button onClick={() => setShowModal(false)} className="px-4 py-2 text-slate-600 border rounded-lg hover:bg-slate-50">Annulla</button>
|
|
<button type="submit" form="createForm" className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-bold">{isEditing ? 'Aggiorna Progetto' : 'Crea Progetto'}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* DETAILS MODAL */}
|
|
{showDetailsModal && selectedExpense && (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-5xl p-6 animate-in fade-in zoom-in duration-200 flex flex-col max-h-[90vh]">
|
|
<div className="flex justify-between items-center mb-6 border-b pb-4">
|
|
<div>
|
|
<h3 className="font-bold text-xl text-slate-800">{selectedExpense.title}</h3>
|
|
<p className="text-sm text-slate-500">Totale: € {selectedExpense.totalAmount.toLocaleString()}</p>
|
|
</div>
|
|
<button onClick={() => setShowDetailsModal(false)}><X className="w-6 h-6 text-slate-400"/></button>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Left: Info & Items */}
|
|
<div className="lg:col-span-1 space-y-6">
|
|
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
|
|
<h4 className="font-bold text-sm text-slate-700 mb-2 uppercase">Dettagli</h4>
|
|
<p className="text-sm text-slate-600 mb-2">{selectedExpense.description}</p>
|
|
<div className="space-y-1 text-sm">
|
|
<p><strong>Appaltatore:</strong> {selectedExpense.contractorName}</p>
|
|
<p><strong>Inizio:</strong> {new Date(selectedExpense.startDate).toLocaleDateString()}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className="font-bold text-sm text-slate-700 mb-2 uppercase">Voci di Spesa</h4>
|
|
<ul className="divide-y border rounded-lg">
|
|
{selectedExpense.items.map((item, i) => (
|
|
<li key={i} className="p-3 text-sm flex justify-between">
|
|
<span>{item.description}</span>
|
|
<span className="font-medium">€ {item.amount}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
{selectedExpense.attachments && selectedExpense.attachments.length > 0 && (
|
|
<div>
|
|
<h4 className="font-bold text-sm text-slate-700 mb-2 uppercase">Documenti</h4>
|
|
<div className="flex flex-wrap gap-2">
|
|
{selectedExpense.attachments.map(att => (
|
|
<button key={att.id} onClick={() => openAttachment(selectedExpense.id, att.id)} className="flex items-center gap-1 bg-white border px-3 py-1 rounded text-xs hover:bg-slate-50">
|
|
<Paperclip className="w-3 h-3"/> {att.fileName}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Right: Shares & Payments */}
|
|
<div className="lg:col-span-2">
|
|
<h4 className="font-bold text-sm text-slate-700 mb-4 uppercase flex items-center gap-2">
|
|
<Euro className="w-4 h-4"/> Stato Pagamenti Famiglie
|
|
</h4>
|
|
<div className="bg-white border rounded-lg overflow-hidden">
|
|
<table className="w-full text-sm text-left">
|
|
<thead className="bg-slate-100 text-slate-600 font-semibold">
|
|
<tr>
|
|
<th className="p-3">Famiglia</th>
|
|
<th className="p-3 text-right">Quota (%)</th>
|
|
<th className="p-3 text-right">Da Pagare</th>
|
|
<th className="p-3 text-right">Versato</th>
|
|
<th className="p-3 text-center">Stato</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y">
|
|
{selectedExpense.shares?.map(share => (
|
|
<tr key={share.id}>
|
|
<td className="p-3 font-medium">{share.familyName}</td>
|
|
<td className="p-3 text-right">{share.percentage}%</td>
|
|
<td className="p-3 text-right font-bold">€ {share.amountDue.toFixed(2)}</td>
|
|
<td className="p-3 text-right text-blue-600">€ {share.amountPaid.toFixed(2)}</td>
|
|
<td className="p-3 text-center">
|
|
<span className={`px-2 py-1 rounded-full text-[10px] font-bold uppercase ${
|
|
share.status === 'PAID' ? 'bg-green-100 text-green-700' :
|
|
share.status === 'PARTIAL' ? 'bg-yellow-100 text-yellow-700' :
|
|
'bg-red-100 text-red-700'
|
|
}`}>
|
|
{share.status === 'PAID' ? 'Saldato' : share.status === 'PARTIAL' ? 'Parziale' : 'Insoluto'}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
<tfoot className="bg-slate-50 font-bold text-slate-700">
|
|
<tr>
|
|
<td colSpan={2} className="p-3 text-right">Totale</td>
|
|
<td className="p-3 text-right">€ {selectedExpense.totalAmount.toLocaleString()}</td>
|
|
<td className="p-3 text-right text-blue-600">
|
|
€ {selectedExpense.shares?.reduce((a,b) => a + b.amountPaid, 0).toLocaleString()}
|
|
</td>
|
|
<td></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|