236 lines
15 KiB
JavaScript
236 lines
15 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { Head, router } from '@inertiajs/react';
|
|
import AdminLayout from '@/Layouts/AdminLayout';
|
|
import {
|
|
Scale,
|
|
MessageSquare,
|
|
Download,
|
|
BadgeDollarSign,
|
|
AlertCircle,
|
|
CheckCircle,
|
|
FileText,
|
|
ArrowRight,
|
|
ArrowUpRight,
|
|
User,
|
|
ShieldCheck,
|
|
Send
|
|
} from 'lucide-react';
|
|
import { Badge } from '@/components/ui/badge';
|
|
|
|
export default function DisputesHub({ tickets }) {
|
|
const [selectedTicket, setSelectedTicket] = useState(tickets[0] || null);
|
|
|
|
// Notes / Resolution states
|
|
const [internalNote, setInternalNote] = useState('');
|
|
const [resolutionAction, setResolutionAction] = useState('refund_employer');
|
|
const [resolutionSummary, setResolutionSummary] = useState('');
|
|
|
|
const handleAddNote = (e) => {
|
|
e.preventDefault();
|
|
if (!internalNote.trim()) return;
|
|
|
|
router.post(`/admin/disputes/${selectedTicket.id}/add-note`, { note: internalNote }, {
|
|
onSuccess: () => {
|
|
if (selectedTicket) {
|
|
selectedTicket.admin_notes += "\n• " + internalNote;
|
|
}
|
|
setInternalNote('');
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleResolve = (e) => {
|
|
e.preventDefault();
|
|
router.post(`/admin/disputes/${selectedTicket.id}/resolve`, {
|
|
resolution: resolutionSummary,
|
|
action_taken: resolutionAction
|
|
}, {
|
|
onSuccess: () => {
|
|
if (selectedTicket) {
|
|
selectedTicket.status = 'Resolved';
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
return (
|
|
<AdminLayout title="Disputes Management Hub">
|
|
<Head title="Disputes Management" />
|
|
|
|
<div className="font-sans max-w-7xl mx-auto space-y-6">
|
|
|
|
{/* Header Row */}
|
|
<div>
|
|
<h1 className="text-xl font-bold text-gray-900 tracking-tight">Contract & Payment Dispute Resolution Center</h1>
|
|
<p className="text-xs text-gray-500 mt-0.5">Audit employer-worker chat history transcripts, review visa fee dispute receipts, and settle escrow refund transfers.</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 items-start">
|
|
|
|
{/* Column 1: Disputes List */}
|
|
<div className="space-y-4">
|
|
<div className="bg-white p-4 border border-slate-200 rounded-2xl shadow-sm">
|
|
<span className="text-[9px] font-black text-slate-400 uppercase tracking-widest block mb-3">Open Dispute Tickets</span>
|
|
<div className="space-y-2">
|
|
{tickets.map((ticket) => (
|
|
<div
|
|
key={ticket.id}
|
|
onClick={() => setSelectedTicket(ticket)}
|
|
className={`p-4 rounded-xl border cursor-pointer transition-all ${
|
|
selectedTicket?.id === ticket.id
|
|
? 'bg-emerald-50/50 border-[#0F6E56] shadow-sm'
|
|
: 'bg-white border-slate-100 hover:bg-slate-50'
|
|
}`}
|
|
>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="font-mono text-[9px] text-slate-400 font-bold">{ticket.id}</span>
|
|
<Badge className={`border-none text-[8px] font-black uppercase ${
|
|
ticket.status === 'Open' ? 'bg-amber-100 text-amber-700' :
|
|
ticket.status === 'Resolved' ? 'bg-emerald-100 text-emerald-800' : 'bg-blue-100 text-blue-700'
|
|
}`}>{ticket.status}</Badge>
|
|
</div>
|
|
<h4 className="text-xs font-black text-slate-900 tracking-tight leading-tight">{ticket.subject}</h4>
|
|
<div className="flex items-center justify-between mt-3 text-[10px] text-slate-400 font-bold">
|
|
<span>AED {ticket.amount_aed}</span>
|
|
<span>{ticket.created_at}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Column 2: Audit Logs & Chat Logs */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
{selectedTicket ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
{/* Chat Log Audit */}
|
|
<div className="bg-white border border-slate-200 rounded-3xl p-5 shadow-sm flex flex-col justify-between h-[520px]">
|
|
<div>
|
|
<h3 className="text-xs font-black text-slate-800 uppercase tracking-widest flex items-center gap-1">
|
|
<MessageSquare className="w-4 h-4 text-[#0F6E56]" />
|
|
<span>Direct Chat logs Transcript</span>
|
|
</h3>
|
|
<p className="text-[9px] text-slate-400 font-semibold mt-1">Official authenticated conversation audit log</p>
|
|
</div>
|
|
|
|
{/* Conversation thread */}
|
|
<div className="flex-1 overflow-y-auto space-y-3 my-4 pr-2 max-h-[360px]">
|
|
{selectedTicket.chat_logs.map((log, i) => {
|
|
const isEmployer = log.sender === 'Employer';
|
|
return (
|
|
<div
|
|
key={i}
|
|
className={`max-w-[85%] p-3 rounded-2xl text-[11px] font-bold shadow-sm ${
|
|
isEmployer
|
|
? 'bg-blue-50 text-blue-900 mr-auto border border-blue-100'
|
|
: 'bg-teal-50 text-teal-900 ml-auto border border-teal-100'
|
|
}`}
|
|
>
|
|
<span className="text-[8px] font-black uppercase text-slate-400 block mb-1">{log.sender} • {log.time}</span>
|
|
<p className="font-semibold leading-relaxed">{log.message}</p>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<div className="text-[10px] bg-slate-50 p-2.5 rounded-xl border border-slate-100 text-slate-400 font-semibold text-center uppercase tracking-wide">
|
|
Logs Digitally Secured by UAE Vetting Compliance
|
|
</div>
|
|
</div>
|
|
|
|
{/* Evidence & Resolution Tools */}
|
|
<div className="space-y-6">
|
|
{/* Evidence Files */}
|
|
<div className="bg-white border border-slate-200 rounded-3xl p-5 shadow-sm space-y-3">
|
|
<h3 className="text-xs font-black text-slate-800 uppercase tracking-widest flex items-center gap-1">
|
|
<FileText className="w-4 h-4 text-[#0F6E56]" />
|
|
<span>Evidence Files</span>
|
|
</h3>
|
|
{selectedTicket.evidence?.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{selectedTicket.evidence.map((file, i) => (
|
|
<div key={i} className="flex items-center justify-between p-2.5 bg-slate-50 rounded-xl border text-xs font-bold text-slate-700">
|
|
<span>{file.name} ({file.type})</span>
|
|
<a href={file.url} className="text-[#0F6E56] hover:underline flex items-center gap-0.5">
|
|
<span>View File</span>
|
|
<ArrowUpRight className="w-3.5 h-3.5" />
|
|
</a>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-[10px] text-slate-400 font-semibold uppercase">No attachment proofs uploaded.</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Admin Notes */}
|
|
<div className="bg-yellow-50/50 border border-yellow-200 rounded-3xl p-5 shadow-sm space-y-3">
|
|
<h3 className="text-xs font-black text-yellow-800 uppercase tracking-widest">Internal Compliance Notes</h3>
|
|
<p className="text-[11px] text-slate-600 bg-white p-3 rounded-xl border border-yellow-100 font-semibold leading-relaxed whitespace-pre-line">
|
|
{selectedTicket.admin_notes}
|
|
</p>
|
|
<form onSubmit={handleAddNote} className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
className="flex-1 bg-white border border-slate-200 rounded-xl px-3 py-2 text-xs font-bold focus:ring-2 focus:ring-yellow-500/10 outline-none"
|
|
placeholder="Write compliance log note..."
|
|
value={internalNote}
|
|
onChange={e => setInternalNote(e.target.value)}
|
|
/>
|
|
<button type="submit" className="p-2 bg-[#0F6E56] text-white rounded-xl">
|
|
<Send className="w-4.5 h-4.5" />
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Resolution Form */}
|
|
{selectedTicket.status !== 'Resolved' && (
|
|
<div className="bg-white border border-slate-200 rounded-3xl p-5 shadow-sm space-y-4">
|
|
<div>
|
|
<h3 className="text-xs font-black text-slate-800 uppercase tracking-widest">Settle Ticket Resolution</h3>
|
|
<p className="text-[10px] text-slate-400 font-semibold mt-1">Submit official dispute settlement outcome</p>
|
|
</div>
|
|
<form onSubmit={handleResolve} className="space-y-3 text-xs font-bold text-slate-700">
|
|
<div className="space-y-1">
|
|
<label className="text-[9px] font-black text-slate-400 uppercase tracking-widest">Resolution Verdict</label>
|
|
<select
|
|
className="w-full bg-slate-50 border rounded-xl px-3 py-2.5 outline-none cursor-pointer focus:bg-white"
|
|
value={resolutionAction}
|
|
onChange={e => setResolutionAction(e.target.value)}
|
|
>
|
|
<option value="refund_employer">Approve Full Refund to Employer (AED {selectedTicket.amount_aed})</option>
|
|
<option value="partial_refund">Approve 50% split partial refund</option>
|
|
<option value="payout_worker">Dismiss dispute & release payout to worker</option>
|
|
</select>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<textarea
|
|
rows="2"
|
|
placeholder="Provide brief explanation for payment release/refund decisions..."
|
|
className="w-full bg-white border rounded-xl p-3 font-semibold focus:ring-2 focus:ring-[#0F6E56]/10 outline-none"
|
|
value={resolutionSummary}
|
|
onChange={e => setResolutionSummary(e.target.value)}
|
|
/>
|
|
</div>
|
|
<button type="submit" className="w-full py-2.5 bg-slate-900 hover:bg-slate-800 text-white rounded-xl text-[10px] font-black uppercase tracking-widest shadow-md">
|
|
Submit Official Verdict
|
|
</button>
|
|
</form>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="bg-white rounded-3xl border p-20 text-center text-slate-400 font-bold">
|
|
Select a dispute ticket from the left panel.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AdminLayout>
|
|
);
|
|
}
|