Update ClientPortal.tsx
This commit is contained in:
@@ -141,18 +141,42 @@ export const ClientPortal: React.FC<ClientPortalProps> = ({
|
|||||||
setIsTyping(false);
|
setIsTyping(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitTicket = (e: React.FormEvent) => {
|
const submitTicket = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
setIsUploading(true);
|
||||||
const attachments: Attachment[] = [];
|
const attachments: Attachment[] = [];
|
||||||
if (ticketFiles) {
|
|
||||||
|
if (ticketFiles && ticketFiles.length > 0) {
|
||||||
for (let i = 0; i < ticketFiles.length; i++) {
|
for (let i = 0; i < ticketFiles.length; i++) {
|
||||||
const file = ticketFiles[i];
|
const file = ticketFiles[i];
|
||||||
attachments.push({
|
|
||||||
id: `att-${Date.now()}-${i}`,
|
// Validation check before upload
|
||||||
name: file.name,
|
const maxSize = (settings.features.maxFileSizeMb || 5) * 1024 * 1024;
|
||||||
type: file.type,
|
if (file.size > maxSize) {
|
||||||
url: URL.createObjectURL(file) // Note: This is client-side only URL for preview, real implementation would upload here too or later
|
showToast(`File ${file.name} troppo grande. Max ${settings.features.maxFileSizeMb}MB`, 'error');
|
||||||
});
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/upload', { method: 'POST', body: formData });
|
||||||
|
if(res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
attachments.push({
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
url: data.url,
|
||||||
|
type: data.type
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showToast(`Errore caricamento ${file.name}`, 'error');
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.error(err);
|
||||||
|
showToast(`Errore di rete caricamento ${file.name}`, 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +191,7 @@ export const ClientPortal: React.FC<ClientPortalProps> = ({
|
|||||||
|
|
||||||
setTicketForm({ subject: '', description: '', queue: queues.length > 0 ? queues[0].name : 'General' });
|
setTicketForm({ subject: '', description: '', queue: queues.length > 0 ? queues[0].name : 'General' });
|
||||||
setTicketFiles(null);
|
setTicketFiles(null);
|
||||||
|
setIsUploading(false);
|
||||||
setActiveView('dashboard');
|
setActiveView('dashboard');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -390,6 +415,20 @@ export const ClientPortal: React.FC<ClientPortalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
<div className="bg-gray-50 p-4 rounded-lg">{selectedTicket.description}</div>
|
<div className="bg-gray-50 p-4 rounded-lg">{selectedTicket.description}</div>
|
||||||
|
|
||||||
|
{selectedTicket.attachments && selectedTicket.attachments.length > 0 && (
|
||||||
|
<div className="bg-blue-50/50 p-4 rounded-lg border border-blue-100">
|
||||||
|
<h3 className="text-xs font-bold text-blue-800 uppercase mb-2 flex items-center"><Paperclip className="w-3 h-3 mr-1"/> Allegati Iniziali</h3>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{selectedTicket.attachments.map(att => (
|
||||||
|
<a key={att.id} href={att.url} target="_blank" rel="noopener noreferrer" className="flex items-center text-xs bg-white border border-blue-200 text-blue-600 px-3 py-2 rounded-lg hover:bg-blue-50 transition">
|
||||||
|
<FileText className="w-3 h-3 mr-2 text-blue-400" /> {att.name}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="font-bold text-gray-700">Messaggi</h3>
|
<h3 className="font-bold text-gray-700">Messaggi</h3>
|
||||||
{selectedTicket.messages.map(m => (
|
{selectedTicket.messages.map(m => (
|
||||||
@@ -440,12 +479,22 @@ export const ClientPortal: React.FC<ClientPortalProps> = ({
|
|||||||
<form onSubmit={submitTicket} className="space-y-4">
|
<form onSubmit={submitTicket} className="space-y-4">
|
||||||
<input type="text" placeholder="Oggetto" className="w-full border p-3 rounded-lg" value={ticketForm.subject} onChange={e => setTicketForm({...ticketForm, subject: e.target.value})} required />
|
<input type="text" placeholder="Oggetto" className="w-full border p-3 rounded-lg" value={ticketForm.subject} onChange={e => setTicketForm({...ticketForm, subject: e.target.value})} required />
|
||||||
<textarea placeholder="Descrizione" rows={5} className="w-full border p-3 rounded-lg" value={ticketForm.description} onChange={e => setTicketForm({...ticketForm, description: e.target.value})} required />
|
<textarea placeholder="Descrizione" rows={5} className="w-full border p-3 rounded-lg" value={ticketForm.description} onChange={e => setTicketForm({...ticketForm, description: e.target.value})} required />
|
||||||
<select className="w-full border p-3 rounded-lg" value={ticketForm.queue} onChange={e => setTicketForm({...ticketForm, queue: e.target.value})}>
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{queues.map(q => <option key={q.id} value={q.name}>{q.name}</option>)}
|
<select className="w-full border p-3 rounded-lg bg-white" value={ticketForm.queue} onChange={e => setTicketForm({...ticketForm, queue: e.target.value})}>
|
||||||
</select>
|
{queues.map(q => <option key={q.id} value={q.name}>{q.name}</option>)}
|
||||||
{/* Create Ticket attachment (mock for now as backend requires form data or separate upload) */}
|
</select>
|
||||||
{/* For simplicity in this demo, omitting file upload on creation to keep code lean, or would need same async upload logic */}
|
<div className="relative">
|
||||||
<button type="submit" className="w-full bg-brand-600 text-white py-3 rounded-lg font-bold">Crea Ticket</button>
|
<label className={`flex items-center justify-center w-full border p-3 rounded-lg cursor-pointer hover:bg-gray-50 ${isUploading ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||||
|
{isUploading ? <Loader2 className="w-5 h-5 animate-spin text-brand-600" /> : <Paperclip className="w-5 h-5 text-gray-500 mr-2" />}
|
||||||
|
<span className="text-gray-500 text-sm">{ticketFiles && ticketFiles.length > 0 ? `${ticketFiles.length} file selezionati` : 'Allega file'}</span>
|
||||||
|
<input type="file" className="hidden" multiple onChange={(e) => setTicketFiles(e.target.files)} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" disabled={isUploading} className="w-full bg-brand-600 text-white py-3 rounded-lg font-bold hover:bg-brand-700 disabled:opacity-70 flex items-center justify-center">
|
||||||
|
{isUploading ? <Loader2 className="w-5 h-5 animate-spin mr-2" /> : null}
|
||||||
|
{isUploading ? 'Caricamento in corso...' : 'Crea Ticket'}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user