2026-06-08 15:51:51 +05:30

294 lines
16 KiB
JavaScript

import React, { useState } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import AdminLayout from '@/Layouts/AdminLayout';
import {
LifeBuoy,
MessageSquare,
AlertCircle,
CheckCircle,
Clock,
Search,
Eye,
Plus,
User,
ShieldAlert,
TrendingUp,
Inbox
} from 'lucide-react';
export default function Index({ tickets, filters }) {
const [searchQuery, setSearchQuery] = useState(filters?.search || '');
const [statusFilter, setStatusFilter] = useState(filters?.status || 'all');
const [priorityFilter, setPriorityFilter] = useState(filters?.priority || 'all');
const handleSearch = (e) => {
e.preventDefault();
applyFilters({
search: searchQuery,
status: statusFilter,
priority: priorityFilter,
});
};
const applyFilters = (newFilters) => {
router.get('/admin/tickets', newFilters, {
preserveState: true,
replace: true,
});
};
const handleFilterChange = (key, value) => {
if (key === 'status') {
setStatusFilter(value);
applyFilters({ search: searchQuery, status: value, priority: priorityFilter });
} else if (key === 'priority') {
setPriorityFilter(value);
applyFilters({ search: searchQuery, status: statusFilter, priority: value });
}
};
// Calculate metrics locally from the tickets list
const totalTickets = tickets.length;
const openTickets = tickets.filter(t => t.status === 'open').length;
const inProgressTickets = tickets.filter(t => t.status === 'in_progress').length;
const resolvedTickets = tickets.filter(t => t.status === 'resolved' || t.status === 'closed').length;
const getStatusBadge = (status) => {
switch (status) {
case 'open':
return (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-black uppercase tracking-wider bg-blue-50 text-blue-700 border border-blue-200">
Open
</span>
);
case 'in_progress':
return (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-black uppercase tracking-wider bg-amber-50 text-amber-700 border border-amber-200">
In Progress
</span>
);
case 'resolved':
return (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-black uppercase tracking-wider bg-emerald-50 text-emerald-700 border border-emerald-200">
Resolved
</span>
);
case 'closed':
return (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-black uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">
Closed
</span>
);
default:
return <span className="text-slate-600">{status}</span>;
}
};
const getPriorityBadge = (priority) => {
switch (priority) {
case 'high':
return (
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-black uppercase bg-red-50 text-red-700 border border-red-100">
High
</span>
);
case 'medium':
return (
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-black uppercase bg-blue-50 text-blue-700 border border-blue-100">
Medium
</span>
);
case 'low':
return (
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-black uppercase bg-slate-100 text-slate-600 border border-slate-200">
Low
</span>
);
default:
return <span className="text-slate-600">{priority}</span>;
}
};
return (
<AdminLayout title="Support Tickets">
<Head title="Support Ticket Management - Admin Portal" />
<div className="font-sans max-w-[1600px] mx-auto space-y-6 select-none">
{/* Header Row */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">Support Ticket Center</h1>
<p className="text-sm text-slate-500 mt-1">Review user issues, update priorities, and generate Senior Developer support responses.</p>
</div>
</div>
{/* Upper Metric Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Metric 1 */}
<div className="bg-white p-5 border border-slate-200 rounded-2xl shadow-sm flex items-center justify-between">
<div className="space-y-1">
<span className="text-xs text-slate-500 font-semibold block">Total Tickets</span>
<span className="text-2xl font-extrabold text-slate-900 block">{totalTickets}</span>
<span className="text-[10px] text-slate-400 font-medium block">All-time received tickets</span>
</div>
<div className="p-3 bg-[#185FA5]/10 text-[#185FA5] rounded-2xl">
<LifeBuoy className="w-6 h-6 animate-spin-slow" />
</div>
</div>
{/* Metric 2 */}
<div className="bg-white p-5 border border-slate-200 rounded-2xl shadow-sm flex items-center justify-between">
<div className="space-y-1">
<span className="text-xs text-slate-500 font-semibold block">New / Open</span>
<span className="text-2xl font-extrabold text-slate-900 block">{openTickets}</span>
<span className="text-[10px] text-rose-500 font-bold block">Needs immediate reply</span>
</div>
<div className="p-3 bg-red-50 text-red-600 rounded-2xl">
<Clock className="w-6 h-6 animate-pulse" />
</div>
</div>
{/* Metric 3 */}
<div className="bg-white p-5 border border-slate-200 rounded-2xl shadow-sm flex items-center justify-between">
<div className="space-y-1">
<span className="text-xs text-slate-500 font-semibold block">In Progress</span>
<span className="text-2xl font-extrabold text-slate-900 block">{inProgressTickets}</span>
<span className="text-[10px] text-amber-500 font-bold block">Active investigation</span>
</div>
<div className="p-3 bg-amber-50 text-amber-600 rounded-2xl">
<MessageSquare className="w-6 h-6" />
</div>
</div>
{/* Metric 4 */}
<div className="bg-white p-5 border border-slate-200 rounded-2xl shadow-sm flex items-center justify-between">
<div className="space-y-1">
<span className="text-xs text-slate-500 font-semibold block">Resolved / Closed</span>
<span className="text-2xl font-extrabold text-slate-900 block">{resolvedTickets}</span>
<span className="text-[10px] text-emerald-600 font-bold block">Archived cleanly</span>
</div>
<div className="p-3 bg-emerald-50 text-emerald-600 rounded-2xl">
<CheckCircle className="w-6 h-6" />
</div>
</div>
</div>
{/* Filters Row */}
<div className="bg-white border border-slate-200 rounded-2xl shadow-sm overflow-hidden">
<div className="p-4 border-b border-slate-100 flex flex-col lg:flex-row lg:items-center justify-between gap-4 bg-slate-50/20">
{/* Search form */}
<form onSubmit={handleSearch} className="relative w-full lg:w-80">
<Search className="w-4 h-4 text-slate-400 absolute left-3.5 top-1/2 -translate-y-1/2" />
<input
type="text"
placeholder="Search ticket # or subject..."
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
className="bg-white border border-slate-200 pl-10 pr-4 py-2 rounded-xl text-xs font-semibold focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 w-full shadow-sm"
/>
</form>
{/* Dropdown Filters */}
<div className="flex flex-wrap items-center gap-3">
<select
value={statusFilter}
onChange={e => handleFilterChange('status', e.target.value)}
className="bg-white border border-slate-200 text-slate-700 text-xs font-bold px-3 py-2 rounded-xl outline-none cursor-pointer hover:bg-slate-50 transition-colors shadow-sm"
>
<option value="all">All Status</option>
<option value="open">Open</option>
<option value="in_progress">In Progress</option>
<option value="resolved">Resolved</option>
<option value="closed">Closed</option>
</select>
<select
value={priorityFilter}
onChange={e => handleFilterChange('priority', e.target.value)}
className="bg-white border border-slate-200 text-slate-700 text-xs font-bold px-3 py-2 rounded-xl outline-none cursor-pointer hover:bg-slate-50 transition-colors shadow-sm"
>
<option value="all">All Priority</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
</div>
{/* Table listing */}
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs font-semibold text-slate-600">
<thead>
<tr className="border-b border-slate-100 bg-slate-50/50 text-slate-400 font-bold uppercase tracking-wider text-[10px]">
<th className="px-5 py-4">Ticket Number</th>
<th className="px-5 py-4">Subject</th>
<th className="px-5 py-4">Raised By</th>
<th className="px-5 py-4">Priority</th>
<th className="px-5 py-4">Status</th>
<th className="px-5 py-4">Created At</th>
<th className="px-5 py-4 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{tickets.map((ticket) => (
<tr key={ticket.id} className="hover:bg-slate-50/40 transition-colors">
<td className="px-5 py-4 font-mono font-bold text-[#185FA5]">
{ticket.ticket_number}
</td>
<td className="px-5 py-4 font-semibold text-slate-800 max-w-xs truncate">
{ticket.subject}
</td>
<td className="px-5 py-4">
<div>
<span className="font-extrabold text-slate-800 block">
{ticket.creator_name}
</span>
<span className={`text-[9px] font-bold block uppercase tracking-wider ${
ticket.user_type === 'Employer' ? 'text-[#185FA5]' : 'text-emerald-600'
}`}>
{ticket.user_type}
</span>
</div>
</td>
<td className="px-5 py-4">
{getPriorityBadge(ticket.priority)}
</td>
<td className="px-5 py-4">
{getStatusBadge(ticket.status)}
</td>
<td className="px-5 py-4 text-slate-400">
{ticket.created_at}
</td>
<td className="px-5 py-4 text-right">
<Link
href={`/admin/tickets/${ticket.id}`}
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-white border border-slate-200 text-slate-700 rounded-lg hover:bg-slate-50 transition-colors shadow-sm font-bold uppercase tracking-wider text-[10px]"
>
<Eye className="w-3.5 h-3.5 text-slate-400" />
<span>Manage</span>
</Link>
</td>
</tr>
))}
{tickets.length === 0 && (
<tr>
<td colSpan="7" className="p-12 text-center font-bold text-slate-400 uppercase">
<div className="flex flex-col items-center justify-center space-y-2">
<Inbox className="w-8 h-8 text-slate-300" />
<span>No support tickets found matching criteria</span>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</AdminLayout>
);
}