344 lines
19 KiB
JavaScript
344 lines
19 KiB
JavaScript
import React from 'react';
|
|
import { Head, Link, useForm } from '@inertiajs/react';
|
|
import EmployerLayout from '../../../Layouts/EmployerLayout';
|
|
import { useTranslation } from '../../../lib/LanguageContext';
|
|
import {
|
|
ArrowLeft,
|
|
Clock,
|
|
CheckCircle,
|
|
AlertCircle,
|
|
CornerDownRight,
|
|
Send,
|
|
Terminal,
|
|
UserCheck,
|
|
Lock,
|
|
UploadCloud,
|
|
X,
|
|
Volume2
|
|
} from 'lucide-react';
|
|
|
|
export default function Show({ ticket, replies }) {
|
|
const { t } = useTranslation();
|
|
|
|
const { data, setData, post, processing, reset, errors } = useForm({
|
|
message: '',
|
|
voice_note: null,
|
|
});
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
post(`/employer/support/${ticket.id}/reply`, {
|
|
onSuccess: () => reset('message', 'voice_note'),
|
|
});
|
|
};
|
|
|
|
const getStatusBadge = (status) => {
|
|
switch (status) {
|
|
case 'open':
|
|
return (
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-black bg-blue-50 text-blue-700 border border-blue-200">
|
|
<Clock className="w-3.5 h-3.5 mr-1" />
|
|
{t('ticket_status_open', 'Open')}
|
|
</span>
|
|
);
|
|
case 'in_progress':
|
|
return (
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-black bg-amber-50 text-amber-700 border border-amber-200">
|
|
<Clock className="w-3.5 h-3.5 mr-1" />
|
|
{t('ticket_status_in_progress', 'In Progress')}
|
|
</span>
|
|
);
|
|
case 'resolved':
|
|
return (
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-black bg-emerald-50 text-emerald-700 border border-emerald-200">
|
|
<CheckCircle className="w-3.5 h-3.5 mr-1" />
|
|
{t('ticket_status_resolved', 'Resolved')}
|
|
</span>
|
|
);
|
|
case 'closed':
|
|
return (
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-black bg-slate-100 text-slate-600 border border-slate-200">
|
|
<AlertCircle className="w-3.5 h-3.5 mr-1" />
|
|
{t('ticket_status_closed', 'Closed')}
|
|
</span>
|
|
);
|
|
default:
|
|
return (
|
|
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-black bg-slate-50 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 tracking-wider bg-rose-50 text-rose-700 border border-rose-100">
|
|
{t('priority_high', 'High')}
|
|
</span>
|
|
);
|
|
case 'medium':
|
|
return (
|
|
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-wider bg-blue-50 text-blue-700 border border-blue-100">
|
|
{t('priority_medium', 'Medium')}
|
|
</span>
|
|
);
|
|
case 'low':
|
|
return (
|
|
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-wider bg-slate-100 text-slate-600 border border-slate-200">
|
|
{t('priority_low', 'Low')}
|
|
</span>
|
|
);
|
|
default:
|
|
return <span className="text-slate-600">{priority}</span>;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<EmployerLayout title={t('ticket_details', 'Ticket Details')}>
|
|
<Head title={`${ticket.ticket_number} - Support Ticket - Employer Portal`} />
|
|
|
|
<div className="max-w-4xl select-none w-full pb-24 space-y-6">
|
|
|
|
{/* Back button */}
|
|
<Link
|
|
href="/employer/support"
|
|
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>{t('back_to_support', 'Back to Help & Support')}</span>
|
|
</Link>
|
|
|
|
{/* Ticket main card */}
|
|
<div className="bg-white rounded-3xl border border-slate-200 shadow-sm overflow-hidden">
|
|
<div className="p-6 md:p-8 border-b border-slate-100 bg-slate-50/40 space-y-4">
|
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
<div className="flex items-center space-x-3">
|
|
<span className="text-xs font-mono font-bold text-slate-400 bg-white px-2.5 py-1 rounded-lg border border-slate-200 shadow-xs">
|
|
{ticket.ticket_number}
|
|
</span>
|
|
{getPriorityBadge(ticket.priority)}
|
|
{getStatusBadge(ticket.status)}
|
|
</div>
|
|
<span className="text-[10px] text-slate-400 font-bold">
|
|
{t('raised_on', 'Raised on:')} {ticket.created_at}
|
|
</span>
|
|
</div>
|
|
{ticket.reason && (
|
|
<div className="inline-flex items-center space-x-1.5 px-3 py-1 bg-indigo-50/80 border border-indigo-100/50 rounded-xl text-indigo-700 font-bold text-[10px] select-none">
|
|
<span>Support Category:</span>
|
|
<span className="font-extrabold">{ticket.reason}</span>
|
|
</div>
|
|
)}
|
|
<h2 className="text-base md:text-lg font-black text-slate-900 tracking-tight leading-snug">
|
|
{ticket.subject}
|
|
</h2>
|
|
</div>
|
|
|
|
<div className="p-6 md:p-8 space-y-4">
|
|
<div className="space-y-1">
|
|
<label className="block text-slate-400 uppercase tracking-widest text-[9px] font-black mb-2">{t('original_message', 'Original Message')}</label>
|
|
<div className="text-xs text-slate-600 font-medium leading-relaxed whitespace-pre-wrap bg-slate-50/50 p-4 rounded-2xl border border-slate-100">
|
|
{ticket.description}
|
|
</div>
|
|
</div>
|
|
|
|
{ticket.voice_note_url && (
|
|
<div className="pt-2">
|
|
<label className="block text-slate-400 uppercase tracking-widest text-[9px] font-black mb-2">{t('voice_note_attachment', 'Voice Note Attachment')}</label>
|
|
<div className="flex items-center space-x-3 bg-slate-50 border border-slate-200 p-3 rounded-2xl max-w-md">
|
|
<audio
|
|
src={ticket.voice_note_url}
|
|
controls
|
|
className="w-full h-8 outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Replies header */}
|
|
<div className="space-y-4 pt-4">
|
|
<h3 className="font-black text-slate-800 text-sm flex items-center space-x-2">
|
|
<CornerDownRight className="w-4 h-4 text-[#185FA5]" />
|
|
<span>{t('ticket_replies', 'Discussion History')} ({replies.length})</span>
|
|
</h3>
|
|
|
|
{/* Replies list */}
|
|
<div className="space-y-4">
|
|
{replies.map((reply) => {
|
|
const isSystemDev = reply.is_developer_response;
|
|
const isAdmin = reply.is_admin;
|
|
|
|
return (
|
|
<div
|
|
key={reply.id}
|
|
className={`p-5 rounded-3xl border transition-all ${
|
|
isSystemDev
|
|
? '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 select-none text-[10px] font-bold border-current/10">
|
|
<div className="flex items-center space-x-2">
|
|
{isSystemDev ? (
|
|
<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={isSystemDev ? 'text-emerald-400 font-mono font-bold' : 'text-slate-800'}>
|
|
{reply.sender_name}
|
|
</span>
|
|
</div>
|
|
<span className="opacity-60">{reply.created_at}</span>
|
|
</div>
|
|
{reply.message && (
|
|
<div className={`text-xs font-semibold leading-relaxed whitespace-pre-wrap ${
|
|
isSystemDev ? 'text-slate-300 font-mono font-semibold' : 'text-slate-600'
|
|
}`}>
|
|
{reply.message}
|
|
</div>
|
|
)}
|
|
|
|
{reply.voice_note_url && (
|
|
<div className="mt-2.5">
|
|
<div className="flex items-center space-x-3 bg-slate-50 border border-slate-100 p-2.5 rounded-2xl max-w-xs">
|
|
<audio
|
|
src={reply.voice_note_url}
|
|
controls
|
|
className="w-full h-8 outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{replies.length === 0 && (
|
|
<div className="p-8 text-center text-xs text-slate-400 font-bold bg-white rounded-2xl border border-slate-100">
|
|
{t('no_replies_yet', 'No replies posted yet. Our team is investigating.')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Reply Form */}
|
|
{ticket.status !== 'closed' ? (
|
|
<div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-sm space-y-4 pt-6">
|
|
<div className="space-y-1">
|
|
<h4 className="font-extrabold text-sm text-slate-900">{t('post_a_reply', 'Post a Reply')}</h4>
|
|
<p className="text-[10px] text-slate-400 font-bold">{t('post_a_reply_desc', 'Need to add info or follow up? Type your response below.')}</p>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="text-xs font-bold text-slate-700">
|
|
<textarea
|
|
rows="4"
|
|
placeholder={t('reply_placeholder', 'Type your reply message here...')}
|
|
value={data.message}
|
|
onChange={e => setData('message', e.target.value)}
|
|
className={`w-full p-3 border rounded-xl bg-slate-50/50 text-slate-800 outline-none focus:bg-white focus:border-[#185FA5] transition-all leading-relaxed ${
|
|
errors.message ? 'border-red-400 bg-red-50/20' : 'border-slate-200'
|
|
}`}
|
|
required={!data.voice_note}
|
|
/>
|
|
{errors.message && (
|
|
<p className="text-[10px] text-red-500 font-semibold mt-1">{errors.message}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Voice Note Attachment */}
|
|
<div className="space-y-1.5">
|
|
<label className="block text-slate-400 uppercase tracking-widest text-[9px] font-black">{t('voice_note_reply', 'Voice Note Attachment (Optional)')}</label>
|
|
{!data.voice_note ? (
|
|
<div className="border-2 border-dashed border-slate-200 rounded-2xl p-4 text-center space-y-1 relative hover:border-[#185FA5] transition-all bg-slate-50/50">
|
|
<UploadCloud className="w-6 h-6 text-slate-400 mx-auto" />
|
|
<div className="text-[11px] font-bold text-slate-600">{t('upload_voice_note', 'Upload a voice note or audio file')}</div>
|
|
<input
|
|
type="file"
|
|
accept="audio/*"
|
|
onChange={e => setData('voice_note', e.target.files[0])}
|
|
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="border border-slate-200 rounded-2xl p-3 bg-slate-50/70 space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-2.5 min-w-0">
|
|
<Volume2 className="w-4 h-4 text-[#185FA5] flex-shrink-0" />
|
|
<div className="min-w-0">
|
|
<p className="text-[11px] font-extrabold text-slate-800 truncate">{data.voice_note.name}</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setData('voice_note', null)}
|
|
className="p-1 bg-slate-100 hover:bg-slate-200 rounded-full text-slate-500 transition-colors"
|
|
>
|
|
<X className="w-3.5 h-3.5" />
|
|
</button>
|
|
</div>
|
|
<audio
|
|
src={URL.createObjectURL(data.voice_note)}
|
|
controls
|
|
className="w-full h-8 outline-none"
|
|
/>
|
|
</div>
|
|
)}
|
|
{errors.voice_note && (
|
|
<p className="text-[10px] text-red-500 font-semibold mt-1">{errors.voice_note}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-end">
|
|
<button
|
|
type="submit"
|
|
disabled={processing}
|
|
className="px-5 py-2.5 rounded-xl bg-[#185FA5] hover:bg-[#144f8a] text-white transition-all shadow-md disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2 text-xs font-bold"
|
|
>
|
|
{processing ? (
|
|
<>
|
|
<span className="w-3 h-3 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
<span>{t('submitting', 'Posting...')}</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<span>{t('send_reply', '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-3xl p-6 flex items-center justify-between gap-4">
|
|
<div className="flex items-center space-x-3.5">
|
|
<div className="w-10 h-10 rounded-xl bg-slate-200 text-slate-600 flex items-center justify-center flex-shrink-0 border border-slate-300">
|
|
<Lock className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<h4 className="font-extrabold text-xs text-slate-800">{t('ticket_closed_title', 'This support ticket has been closed')}</h4>
|
|
<p className="text-[10px] text-slate-400 font-bold">{t('ticket_closed_desc', 'This discussion is locked. If the problem persists, please create a new support ticket.')}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
</EmployerLayout>
|
|
);
|
|
}
|