311 lines
18 KiB
JavaScript
311 lines
18 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { Head, Link, useForm } from '@inertiajs/react';
|
|
import AdminLayout from '@/Layouts/AdminLayout';
|
|
import {
|
|
ArrowLeft,
|
|
Clock,
|
|
CheckCircle,
|
|
AlertCircle,
|
|
Send,
|
|
Terminal,
|
|
User,
|
|
Mail,
|
|
LifeBuoy,
|
|
Shield,
|
|
Sparkles,
|
|
Loader2
|
|
} from 'lucide-react';
|
|
import axios from 'axios';
|
|
import { toast } from 'sonner';
|
|
|
|
export default function Show({ ticket, replies }) {
|
|
const replyForm = useForm({
|
|
message: '',
|
|
is_developer_response: false,
|
|
close_ticket: false,
|
|
});
|
|
|
|
const statusForm = useForm({
|
|
status: ticket.status,
|
|
});
|
|
|
|
const handleReplySubmit = (e) => {
|
|
e.preventDefault();
|
|
replyForm.post(`/admin/tickets/${ticket.id}/reply`, {
|
|
onSuccess: () => {
|
|
replyForm.reset('message', 'close_ticket');
|
|
toast.success('Reply posted successfully.');
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleStatusChange = (newStatus) => {
|
|
statusForm.setData('status', newStatus);
|
|
statusForm.post(`/admin/tickets/${ticket.id}/status`, {
|
|
onSuccess: () => {
|
|
toast.success('Ticket status updated successfully.');
|
|
}
|
|
});
|
|
};
|
|
|
|
const getStatusColor = (status) => {
|
|
switch (status) {
|
|
case 'open': return 'bg-blue-50 text-blue-700 border-blue-200';
|
|
case 'in_progress': return 'bg-amber-50 text-amber-700 border-amber-200';
|
|
case 'resolved': return 'bg-emerald-50 text-emerald-700 border-emerald-200';
|
|
case 'closed': return 'bg-slate-100 text-slate-600 border-slate-200';
|
|
default: return 'bg-slate-50 text-slate-600';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AdminLayout title={`Ticket #${ticket.ticket_number}`}>
|
|
<Head title={`Ticket ${ticket.ticket_number} - Admin Support`} />
|
|
|
|
<div className="max-w-[1400px] mx-auto space-y-6 select-none pb-24">
|
|
|
|
{/* Back button */}
|
|
<Link
|
|
href="/admin/tickets"
|
|
className="inline-flex items-center space-x-2 text-xs font-black text-slate-500 hover:text-slate-800 transition-colors"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
<span>Back to Support Tickets</span>
|
|
</Link>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">
|
|
|
|
{/* Left Pane: Ticket details and Replies (8 Cols) */}
|
|
<div className="lg:col-span-8 space-y-6">
|
|
|
|
{/* Ticket Card */}
|
|
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
|
|
<div className="p-6 border-b border-slate-100 bg-slate-50/30 flex justify-between items-center flex-wrap gap-4">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center space-x-2">
|
|
<span className="text-xs font-mono font-bold text-slate-400 bg-white px-2 py-0.5 rounded border">
|
|
{ticket.ticket_number}
|
|
</span>
|
|
<span className={`px-2 py-0.5 rounded text-[10px] font-black uppercase ${
|
|
ticket.priority === 'high' ? 'bg-red-50 text-red-700 border border-red-100' :
|
|
ticket.priority === 'medium' ? 'bg-blue-50 text-blue-700 border border-blue-100' :
|
|
'bg-slate-100 text-slate-600 border'
|
|
}`}>
|
|
{ticket.priority} Priority
|
|
</span>
|
|
</div>
|
|
<h2 className="text-base font-black text-slate-900 mt-1">{ticket.subject}</h2>
|
|
</div>
|
|
|
|
{/* Quick status dropdown */}
|
|
<div className="flex items-center space-x-2">
|
|
<span className="text-[10px] text-slate-400 font-bold uppercase">Status:</span>
|
|
<select
|
|
value={statusForm.data.status}
|
|
onChange={e => handleStatusChange(e.target.value)}
|
|
disabled={statusForm.processing}
|
|
className="bg-white border border-slate-200 text-slate-700 text-xs font-bold px-3 py-1.5 rounded-xl outline-none cursor-pointer hover:bg-slate-50 transition-colors"
|
|
>
|
|
<option value="open">Open</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="resolved">Resolved</option>
|
|
<option value="closed">Closed</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-6 space-y-4">
|
|
<div>
|
|
<span className="text-[9px] text-slate-400 uppercase tracking-widest font-black block mb-2">Original Ticket Message</span>
|
|
<div className="bg-slate-50 p-4 rounded-xl border border-slate-150 text-xs text-slate-600 font-medium whitespace-pre-wrap leading-relaxed">
|
|
{ticket.description}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Replies Thread */}
|
|
<div className="space-y-4">
|
|
<h3 className="font-extrabold text-sm text-slate-800 uppercase tracking-wider">Discussion History ({replies.length})</h3>
|
|
|
|
<div className="space-y-4">
|
|
{replies.map((reply) => {
|
|
const isDev = reply.is_developer_response;
|
|
const isAdmin = reply.is_admin;
|
|
|
|
return (
|
|
<div
|
|
key={reply.id}
|
|
className={`p-5 rounded-2xl border transition-all ${
|
|
isDev
|
|
? 'bg-slate-900 text-slate-100 border-slate-800 shadow-md font-mono'
|
|
: isAdmin
|
|
? 'bg-blue-50/60 text-slate-700 border-blue-100'
|
|
: 'bg-white text-slate-700 border-slate-200'
|
|
}`}
|
|
>
|
|
<div className="flex items-center justify-between border-b pb-2 mb-3.5 text-[10px] font-bold border-current/10">
|
|
<div className="flex items-center space-x-2">
|
|
{isDev ? (
|
|
<div className="w-5 h-5 rounded bg-emerald-500 text-slate-950 flex items-center justify-center">
|
|
<Terminal className="w-3.5 h-3.5" />
|
|
</div>
|
|
) : (
|
|
<div className={`w-5 h-5 rounded-full flex items-center justify-center text-[9px] font-black ${
|
|
isAdmin ? 'bg-[#185FA5] text-white' : 'bg-slate-100 text-slate-600'
|
|
}`}>
|
|
{reply.sender_name.charAt(0)}
|
|
</div>
|
|
)}
|
|
<span className={isDev ? 'text-emerald-400 font-mono font-bold' : 'text-slate-800'}>
|
|
{reply.sender_name} {isDev && '(Senior Software Developer)'}
|
|
</span>
|
|
</div>
|
|
<span className="opacity-60">{reply.created_at}</span>
|
|
</div>
|
|
<div className={`text-xs leading-relaxed whitespace-pre-wrap ${
|
|
isDev ? 'text-slate-300 font-mono font-semibold' : 'text-slate-600 font-semibold'
|
|
}`}>
|
|
{reply.message}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{replies.length === 0 && (
|
|
<div className="p-8 text-center text-xs text-slate-400 font-bold bg-white rounded-xl border border-slate-200">
|
|
No messages posted yet.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Reply Form */}
|
|
{ticket.status !== 'closed' ? (
|
|
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
|
<div className="flex items-center justify-between flex-wrap gap-2">
|
|
<div className="space-y-0.5">
|
|
<h4 className="font-extrabold text-sm text-slate-900">Post Reply</h4>
|
|
<p className="text-[10px] text-slate-400 font-semibold">Respond directly to the customer as admin or software developer.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleReplySubmit} className="space-y-4 text-xs font-bold text-slate-700">
|
|
<div>
|
|
<textarea
|
|
rows="4"
|
|
placeholder="Write your response message..."
|
|
value={replyForm.data.message}
|
|
onChange={e => replyForm.setData('message', e.target.value)}
|
|
className={`w-full p-3 border border-slate-200 rounded-xl bg-slate-50/50 text-slate-800 outline-none focus:bg-white focus:border-[#185FA5] transition-all leading-relaxed ${
|
|
replyForm.errors.message ? 'border-red-400' : ''
|
|
}`}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between border-t border-slate-100 pt-4 flex-wrap gap-3">
|
|
{/* Toggles Group */}
|
|
<div className="flex flex-col space-y-2">
|
|
{/* Close ticket toggle */}
|
|
<label className="flex items-center space-x-2.5 cursor-pointer select-none">
|
|
<input
|
|
type="checkbox"
|
|
checked={replyForm.data.close_ticket}
|
|
onChange={e => {
|
|
replyForm.setData('close_ticket', e.target.checked);
|
|
}}
|
|
className="w-4.5 h-4.5 rounded border-slate-300 text-slate-900 focus:ring-slate-900 cursor-pointer"
|
|
/>
|
|
<span className="text-xs text-slate-700 font-extrabold flex items-center space-x-1">
|
|
<CheckCircle className="w-3.5 h-3.5 text-emerald-600" />
|
|
<span>Close Ticket after Reply</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={replyForm.processing}
|
|
className="px-5 py-2.5 bg-[#185FA5] hover:bg-[#144f8a] text-white rounded-xl transition-all shadow-md flex items-center space-x-1.5"
|
|
>
|
|
{replyForm.processing ? (
|
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
) : (
|
|
<>
|
|
<span>Post Reply</span>
|
|
<Send className="w-3.5 h-3.5" />
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
) : (
|
|
<div className="bg-slate-100 border border-slate-200 rounded-2xl p-5 flex items-center space-x-3.5">
|
|
<AlertCircle className="w-5 h-5 text-slate-400" />
|
|
<div className="text-xs font-bold text-slate-700">
|
|
<h4 className="text-slate-800">Closed Support Ticket</h4>
|
|
<p className="text-slate-400 font-semibold mt-0.5">This ticket has been marked closed. Change status above to reopen and reply.</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Right Pane: Creator Profile details (4 Cols) */}
|
|
<div className="lg:col-span-4 space-y-6">
|
|
|
|
{/* Creator Profile Info */}
|
|
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-5">
|
|
<div>
|
|
<h3 className="font-extrabold text-sm text-slate-900">Requester Profile</h3>
|
|
<p className="text-[10px] text-slate-400 font-semibold">User account information associated with ticket</p>
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-3 border-b border-slate-100 pb-4">
|
|
<div className="w-10 h-10 rounded-full bg-slate-100 text-slate-600 flex items-center justify-center font-black border">
|
|
{ticket.creator_name.charAt(0)}
|
|
</div>
|
|
<div>
|
|
<span className="font-extrabold text-xs text-slate-800 block">{ticket.creator_name}</span>
|
|
<span className={`text-[9px] font-bold px-2 py-0.5 rounded-full inline-block mt-0.5 ${
|
|
ticket.user_type === 'Employer' ? 'bg-blue-50 text-[#185FA5] border border-blue-100' : 'bg-emerald-50 text-emerald-700 border border-emerald-100'
|
|
}`}>
|
|
{ticket.user_type} Profile
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-3.5 text-xs font-semibold text-slate-600">
|
|
<div className="flex items-center space-x-2">
|
|
<Mail className="w-4 h-4 text-slate-400" />
|
|
<span className="truncate">{ticket.creator_email}</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Clock className="w-4 h-4 text-slate-400" />
|
|
<span>Registered: {ticket.created_at}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Support compliance tips */}
|
|
<div className="bg-slate-900 p-6 rounded-2xl text-slate-100 border border-slate-800 space-y-3.5">
|
|
<h4 className="text-xs font-black uppercase tracking-wider flex items-center space-x-2 text-emerald-400 font-mono">
|
|
<Shield className="w-4 h-4" />
|
|
<span>Support Compliance Guard</span>
|
|
</h4>
|
|
<ul className="text-[10px] font-semibold text-slate-400 space-y-2 leading-relaxed list-disc list-inside">
|
|
<li>Always verify billing receipts before resolving payment failures.</li>
|
|
<li>Use the Senior Developer response builder to address server exceptions automatically.</li>
|
|
<li>Keep tickets open until client explicitly confirms resolved status.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</AdminLayout>
|
|
);
|
|
}
|