import React, { useEffect, useState } from 'react'; import { CondoService } from '../services/api'; import { Ticket, TicketStatus, TicketPriority, TicketCategory, TicketAttachment, TicketComment } from '../types'; import { MessageSquareWarning, Plus, Search, Filter, Paperclip, X, CheckCircle2, Clock, XCircle, FileIcon, Image as ImageIcon, Film, Send, PauseCircle, Archive, Trash2, User } from 'lucide-react'; export const TicketsPage: React.FC = () => { const user = CondoService.getCurrentUser(); const isAdmin = user?.role === 'admin' || user?.role === 'poweruser'; const [tickets, setTickets] = useState([]); const [loading, setLoading] = useState(true); // View Filters const [viewMode, setViewMode] = useState<'active' | 'archived'>('active'); const [showModal, setShowModal] = useState(false); const [viewTicket, setViewTicket] = useState(null); const [submitting, setSubmitting] = useState(false); // Comments State const [comments, setComments] = useState([]); const [newComment, setNewComment] = useState(''); const [loadingComments, setLoadingComments] = useState(false); // Form State const [formTitle, setFormTitle] = useState(''); const [formDesc, setFormDesc] = useState(''); const [formCategory, setFormCategory] = useState(TicketCategory.OTHER); const [formPriority, setFormPriority] = useState(TicketPriority.MEDIUM); const [attachments, setAttachments] = useState<{fileName: string, fileType: string, data: string}[]>([]); // File Reading helper const handleFileChange = async (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const newAttachments = []; for (let i = 0; i < e.target.files.length; i++) { const file = e.target.files[i]; // Check size (e.g. 5MB limit per file for demo safety) if (file.size > 5 * 1024 * 1024) { alert(`Il file ${file.name} è troppo grande (max 5MB)`); continue; } const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result as string); reader.onerror = error => reject(error); }); newAttachments.push({ fileName: file.name, fileType: file.type, data: base64 }); } setAttachments([...attachments, ...newAttachments]); } }; const removeAttachment = (index: number) => { const newAtt = [...attachments]; newAtt.splice(index, 1); setAttachments(newAtt); }; const loadTickets = async () => { setLoading(true); try { const list = await CondoService.getTickets(); setTickets(list); // Mark tickets page as viewed to clear badges localStorage.setItem('lastViewedTickets', Date.now().toString()); window.dispatchEvent(new Event('tickets-viewed')); } catch (e) { console.error(e); } finally { setLoading(false); } }; const loadComments = async (ticketId: string) => { setLoadingComments(true); try { const data = await CondoService.getTicketComments(ticketId); setComments(data); } catch(e) { console.error(e); } finally { setLoadingComments(false); } }; useEffect(() => { loadTickets(); }, []); useEffect(() => { if (viewTicket) { loadComments(viewTicket.id); } else { setComments([]); } }, [viewTicket]); const handleCreateSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSubmitting(true); try { await CondoService.createTicket({ title: formTitle, description: formDesc, category: formCategory, priority: formPriority, attachments: attachments }); setShowModal(false); // Reset form setFormTitle(''); setFormDesc(''); setAttachments([]); loadTickets(); } catch (e) { alert("Errore creazione ticket"); console.error(e); } finally { setSubmitting(false); } }; const handleStatusUpdate = async (status: TicketStatus) => { if (!viewTicket) return; try { await CondoService.updateTicket(viewTicket.id, { status: status, priority: viewTicket.priority }); // Update local state const updated = { ...viewTicket, status }; setViewTicket(updated); setTickets(tickets.map(t => t.id === updated.id ? updated : t)); } catch (e) { console.error(e); } }; const handleDeleteTicket = async (id: string) => { if (!confirm("Eliminare definitivamente questa segnalazione?")) return; try { await CondoService.deleteTicket(id); setTickets(tickets.filter(t => t.id !== id)); setViewTicket(null); } catch (e: any) { alert(e.message || "Impossibile eliminare."); } }; const handleSendComment = async (e: React.FormEvent) => { e.preventDefault(); if (!viewTicket || !newComment.trim()) return; try { await CondoService.addTicketComment(viewTicket.id, newComment); setNewComment(''); loadComments(viewTicket.id); } catch(e) { console.error(e); } }; // Filter Logic const filteredTickets = tickets.filter(t => { const isArchived = t.status === TicketStatus.RESOLVED || t.status === TicketStatus.CLOSED; if (viewMode === 'active') return !isArchived; return isArchived; }); // Helpers for Badge Colors const getStatusColor = (s: TicketStatus) => { switch(s) { case TicketStatus.OPEN: return 'bg-blue-100 text-blue-700'; case TicketStatus.IN_PROGRESS: return 'bg-yellow-100 text-yellow-700'; case TicketStatus.SUSPENDED: return 'bg-orange-100 text-orange-700'; case TicketStatus.RESOLVED: return 'bg-green-100 text-green-700'; case TicketStatus.CLOSED: return 'bg-slate-200 text-slate-600'; } }; const getPriorityColor = (p: TicketPriority) => { switch(p) { case TicketPriority.LOW: return 'bg-slate-100 text-slate-600'; case TicketPriority.MEDIUM: return 'bg-blue-50 text-blue-600'; case TicketPriority.HIGH: return 'bg-orange-100 text-orange-600'; case TicketPriority.URGENT: return 'bg-red-100 text-red-600'; } }; const getCategoryLabel = (c: TicketCategory) => { switch(c) { case TicketCategory.MAINTENANCE: return 'Manutenzione'; case TicketCategory.ADMINISTRATIVE: return 'Amministrazione'; case TicketCategory.CLEANING: return 'Pulizie'; case TicketCategory.NOISE: return 'Disturbo'; default: return 'Altro'; } }; const openAttachment = async (ticketId: string, attId: string) => { try { const file = await CondoService.getTicketAttachment(ticketId, attId); const win = window.open(); if (win) { if (file.fileType.startsWith('image/')) { win.document.write(``); } else if (file.fileType === 'application/pdf') { win.document.write(``); } else { win.document.write(`Clicca qui per scaricare ${file.fileName}`); } } } catch (e) { alert("Errore apertura file"); } }; return (

Segnalazioni

Gestisci guasti e richieste

{/* Main Tabs */}
{/* List */} {loading ? (
Caricamento...
) : filteredTickets.length === 0 ? (

Nessuna segnalazione in questa vista.

) : (
{filteredTickets.map(ticket => (
setViewTicket(ticket)} className="bg-white p-5 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow cursor-pointer relative group">
{ticket.status.replace('_', ' ')} {ticket.priority}

{ticket.title}

{new Date(ticket.createdAt).toLocaleDateString()} • {getCategoryLabel(ticket.category)}

{ticket.description}

{ticket.userName ? ticket.userName.charAt(0) : '?'}
{ticket.userName || 'Utente'}
{ticket.attachments && ticket.attachments.length > 0 && (
{ticket.attachments.length}
)}
))}
)} {/* CREATE MODAL */} {showModal && (

Nuova Segnalazione

setFormTitle(e.target.value)} required placeholder="Es. Luce scale rotta" />