Update ExtraordinaryUser.tsx

This commit is contained in:
2025-12-10 23:19:18 +01:00
committed by GitHub
parent a34a27ea85
commit 074daa7110

View File

@@ -1,16 +1,22 @@
import React, { useEffect, useState } from 'react';
import { CondoService } from '../services/mockDb';
import { CondoService } from '../services/api';
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";
import { Briefcase, Calendar, CheckCircle2, AlertCircle } from 'lucide-react';
import { Briefcase, Calendar, CheckCircle2, AlertCircle, Eye, Paperclip, X, FileText, Download } from 'lucide-react';
import { ExtraordinaryExpense } from '../types';
export const ExtraordinaryUser: React.FC = () => {
const [expenses, setExpenses] = useState<any[]>([]); // Using any for composite object from specific API
const [expenses, setExpenses] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [paypalClientId, setPaypalClientId] = useState<string>('');
const [successMsg, setSuccessMsg] = useState('');
const [hasFamily, setHasFamily] = useState(true);
// Details Modal State
const [showDetails, setShowDetails] = useState(false);
const [selectedExpense, setSelectedExpense] = useState<ExtraordinaryExpense | null>(null);
const [loadingDetails, setLoadingDetails] = useState(false);
useEffect(() => {
const load = async () => {
try {
@@ -50,6 +56,35 @@ export const ExtraordinaryUser: React.FC = () => {
} catch(e) { alert("Errore registrazione pagamento"); }
};
const handleCardClick = async (expenseId: string) => {
setLoadingDetails(true);
try {
// Fetch full details including attachments
const fullDetails = await CondoService.getExpenseDetails(expenseId);
setSelectedExpense(fullDetails);
setShowDetails(true);
} catch (e) {
console.error(e);
alert("Impossibile caricare i dettagli.");
} finally {
setLoadingDetails(false);
}
};
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 apertura file"); }
};
if (loading) return <div className="p-8 text-center text-slate-400">Caricamento spese...</div>;
if (!hasFamily) {
@@ -88,10 +123,16 @@ export const ExtraordinaryUser: React.FC = () => {
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
key={exp.id}
onClick={() => handleCardClick(exp.id)}
className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden flex flex-col cursor-pointer hover:shadow-md transition-all group"
>
<div className="p-5 border-b border-slate-100 bg-slate-50/50 flex justify-between items-start group-hover:bg-blue-50/30 transition-colors">
<div>
<h3 className="font-bold text-lg text-slate-800">{exp.title}</h3>
<h3 className="font-bold text-lg text-slate-800 group-hover:text-blue-700 transition-colors flex items-center gap-2">
{exp.title} <Eye className="w-4 h-4 text-slate-400 opacity-0 group-hover:opacity-100 transition-opacity"/>
</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>
@@ -126,7 +167,7 @@ export const ExtraordinaryUser: React.FC = () => {
</div>
</div>
<div className="p-5 border-t border-slate-100 bg-slate-50">
<div className="p-5 border-t border-slate-100 bg-slate-50" onClick={(e) => e.stopPropagation()}>
{!isPaid && remaining > 0.01 && (
<>
{paypalClientId ? (
@@ -168,6 +209,102 @@ export const ExtraordinaryUser: React.FC = () => {
})}
</div>
)}
{/* 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="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>
<p className="text-xs text-slate-500 uppercase tracking-wider mt-1">{selectedExpense.contractorName}</p>
</div>
<button onClick={() => setShowDetails(false)} className="p-2 hover:bg-slate-200 rounded-full text-slate-500 transition-colors">
<X className="w-6 h-6" />
</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()}
</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>
</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>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Attachments */}
{selectedExpense.attachments && selectedExpense.attachments.length > 0 && (
<div>
<h4 className="text-sm font-bold text-slate-700 mb-2 flex items-center gap-2"><Paperclip className="w-4 h-4"/> Documenti & Allegati</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{selectedExpense.attachments.map(att => (
<button
key={att.id}
onClick={() => openAttachment(selectedExpense.id, att.id)}
className="flex items-center justify-between p-3 border rounded-lg hover:border-blue-400 hover:bg-blue-50 transition-all text-left group"
>
<div className="flex items-center gap-2 min-w-0">
<div className="bg-slate-100 p-2 rounded group-hover:bg-white transition-colors">
<FileText className="w-4 h-4 text-blue-600"/>
</div>
<span className="text-sm font-medium text-slate-700 truncate">{att.fileName}</span>
</div>
<Download className="w-4 h-4 text-slate-400 group-hover:text-blue-600"/>
</button>
))}
</div>
</div>
)}
</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>
</div>
</div>
</div>
)}
</div>
);
};