update
This commit is contained in:
323
frontEnd/components/pages/support/TicketStatusCheck.tsx
Normal file
323
frontEnd/components/pages/support/TicketStatusCheck.tsx
Normal file
@@ -0,0 +1,323 @@
|
||||
"use client";
|
||||
import { useState, FormEvent } from 'react';
|
||||
import { checkTicketStatus, SupportTicket } from '@/lib/api/supportService';
|
||||
|
||||
const TicketStatusCheck = () => {
|
||||
const [ticketNumber, setTicketNumber] = useState('');
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [searchError, setSearchError] = useState<string | null>(null);
|
||||
const [ticket, setTicket] = useState<SupportTicket | null>(null);
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setIsSearching(true);
|
||||
setSearchError(null);
|
||||
setTicket(null);
|
||||
|
||||
try {
|
||||
const response = await checkTicketStatus(ticketNumber);
|
||||
setTicket(response);
|
||||
} catch (error: any) {
|
||||
if (error.message === 'Ticket not found') {
|
||||
setSearchError('Ticket not found. Please check your ticket number and try again.');
|
||||
} else {
|
||||
setSearchError('An error occurred while searching. Please try again.');
|
||||
}
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
const getRelativeTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
if (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? 's' : ''} ago`;
|
||||
if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;
|
||||
if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;
|
||||
return formatDate(dateString);
|
||||
};
|
||||
|
||||
const getStatusIcon = (statusName: string) => {
|
||||
const status = statusName.toLowerCase();
|
||||
if (status.includes('open') || status.includes('new')) return 'fa-inbox';
|
||||
if (status.includes('progress') || status.includes('working')) return 'fa-spinner';
|
||||
if (status.includes('pending') || status.includes('waiting')) return 'fa-clock';
|
||||
if (status.includes('resolved') || status.includes('closed')) return 'fa-check-circle';
|
||||
return 'fa-ticket';
|
||||
};
|
||||
|
||||
const getPriorityIcon = (priorityName: string) => {
|
||||
const priority = priorityName.toLowerCase();
|
||||
if (priority.includes('urgent') || priority.includes('critical')) return 'fa-exclamation-triangle';
|
||||
if (priority.includes('high')) return 'fa-arrow-up';
|
||||
if (priority.includes('medium')) return 'fa-minus';
|
||||
if (priority.includes('low')) return 'fa-arrow-down';
|
||||
return 'fa-flag';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ticket-status-check enterprise-status-check">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12">
|
||||
<div className="status-header-enterprise">
|
||||
<div className="status-header-icon">
|
||||
<i className="fa-solid fa-magnifying-glass"></i>
|
||||
</div>
|
||||
<h2>Check Ticket Status</h2>
|
||||
<p>Track your support request in real-time with instant status updates</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="status-search-form-enterprise">
|
||||
<div className="search-container">
|
||||
<div className="search-input-wrapper">
|
||||
<i className="fa-solid fa-ticket search-icon"></i>
|
||||
<input
|
||||
type="text"
|
||||
value={ticketNumber}
|
||||
onChange={(e) => setTicketNumber(e.target.value.toUpperCase())}
|
||||
placeholder="TKT-YYYYMMDD-XXXXX"
|
||||
required
|
||||
className="search-input-enterprise"
|
||||
pattern="TKT-\d{8}-[A-Z0-9]{5}"
|
||||
title="Please enter a valid ticket number (e.g., TKT-20231015-ABCDE)"
|
||||
/>
|
||||
{ticketNumber && (
|
||||
<button
|
||||
type="button"
|
||||
className="clear-btn"
|
||||
onClick={() => {
|
||||
setTicketNumber('');
|
||||
setTicket(null);
|
||||
setSearchError(null);
|
||||
}}
|
||||
>
|
||||
<i className="fa-solid fa-times"></i>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-search-enterprise"
|
||||
disabled={isSearching}
|
||||
>
|
||||
{isSearching ? (
|
||||
<>
|
||||
<span className="spinner-enterprise"></span>
|
||||
<span>Searching...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="fa-solid fa-search"></i>
|
||||
<span>Search</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="search-hint">
|
||||
<i className="fa-solid fa-info-circle"></i>
|
||||
Enter your ticket number exactly as it appears in your confirmation email
|
||||
</p>
|
||||
</form>
|
||||
|
||||
{searchError && (
|
||||
<div className="alert-enterprise alert-error">
|
||||
<i className="fa-solid fa-exclamation-circle"></i>
|
||||
<div>
|
||||
<strong>Ticket Not Found</strong>
|
||||
<p>{searchError}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ticket && (
|
||||
<div className="ticket-details-enterprise">
|
||||
{/* Header Section */}
|
||||
<div className="ticket-header-enterprise">
|
||||
<div className="ticket-number-section">
|
||||
<span className="ticket-label">Ticket Number</span>
|
||||
<div className="ticket-number-display">
|
||||
<i className="fa-solid fa-ticket"></i>
|
||||
<span>{ticket.ticket_number}</span>
|
||||
<button
|
||||
className="btn-copy-inline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(ticket.ticket_number);
|
||||
const btn = document.querySelector('.btn-copy-inline');
|
||||
if (btn) {
|
||||
const originalHTML = btn.innerHTML;
|
||||
btn.innerHTML = '<i class="fa-solid fa-check"></i>';
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = originalHTML;
|
||||
}, 2000);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className="fa-solid fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="status-badge-enterprise"
|
||||
style={{
|
||||
backgroundColor: ticket.status_color || '#6366f1',
|
||||
boxShadow: `0 0 20px ${ticket.status_color}33`
|
||||
}}
|
||||
>
|
||||
<i className={`fa-solid ${getStatusIcon(ticket.status_name)}`}></i>
|
||||
<span>{ticket.status_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info Cards Section */}
|
||||
<div className="ticket-info-cards">
|
||||
<div className="info-card">
|
||||
<i className="fa-solid fa-calendar-plus"></i>
|
||||
<div>
|
||||
<span className="card-label">Created</span>
|
||||
<span className="card-value">{getRelativeTime(ticket.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-card">
|
||||
<i className="fa-solid fa-clock"></i>
|
||||
<div>
|
||||
<span className="card-label">Last Updated</span>
|
||||
<span className="card-value">{getRelativeTime(ticket.updated_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{ticket.priority_name && (
|
||||
<div className="info-card">
|
||||
<i className={`fa-solid ${getPriorityIcon(ticket.priority_name)}`}></i>
|
||||
<div>
|
||||
<span className="card-label">Priority</span>
|
||||
<span
|
||||
className="card-value priority-value"
|
||||
style={{ color: ticket.priority_color }}
|
||||
>
|
||||
{ticket.priority_name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{ticket.category_name && (
|
||||
<div className="info-card">
|
||||
<i className="fa-solid fa-folder-open"></i>
|
||||
<div>
|
||||
<span className="card-label">Category</span>
|
||||
<span className="card-value">{ticket.category_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="ticket-content-section">
|
||||
<div className="content-header">
|
||||
<h3>{ticket.title}</h3>
|
||||
<div className="content-meta">
|
||||
<span><i className="fa-solid fa-user"></i> {ticket.user_name}</span>
|
||||
{ticket.company && (
|
||||
<span><i className="fa-solid fa-building"></i> {ticket.company}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="description-section">
|
||||
<h4><i className="fa-solid fa-align-left"></i> Description</h4>
|
||||
<p>{ticket.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Messages Section */}
|
||||
{ticket.messages && ticket.messages.filter(msg => !msg.is_internal).length > 0 && (
|
||||
<div className="messages-section-enterprise">
|
||||
<h4>
|
||||
<i className="fa-solid fa-comments"></i>
|
||||
Conversation
|
||||
<span className="count-badge">{ticket.messages.filter(msg => !msg.is_internal).length}</span>
|
||||
</h4>
|
||||
<div className="messages-list-enterprise">
|
||||
{ticket.messages
|
||||
.filter(msg => !msg.is_internal)
|
||||
.map((message) => (
|
||||
<div key={message.id} className="message-card-enterprise">
|
||||
<div className="message-avatar">
|
||||
<i className="fa-solid fa-user"></i>
|
||||
</div>
|
||||
<div className="message-content-wrapper">
|
||||
<div className="message-header-enterprise">
|
||||
<span className="message-author">{message.author_name || message.author_email}</span>
|
||||
<span className="message-time">{getRelativeTime(message.created_at)}</span>
|
||||
</div>
|
||||
<div className="message-text">
|
||||
{message.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Activity Timeline */}
|
||||
{ticket.activities && ticket.activities.length > 0 && (
|
||||
<div className="timeline-section-enterprise">
|
||||
<h4>
|
||||
<i className="fa-solid fa-list-check"></i>
|
||||
Activity Timeline
|
||||
</h4>
|
||||
<div className="timeline-items">
|
||||
{ticket.activities.slice(0, 5).map((activity, index) => (
|
||||
<div key={activity.id} className="timeline-item-enterprise">
|
||||
<div className="timeline-marker">
|
||||
<i className="fa-solid fa-circle"></i>
|
||||
</div>
|
||||
<div className="timeline-content-wrapper">
|
||||
<div className="timeline-text">{activity.description}</div>
|
||||
<div className="timeline-time">{getRelativeTime(activity.created_at)}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer Actions */}
|
||||
<div className="ticket-footer-enterprise">
|
||||
<div className="footer-info">
|
||||
<i className="fa-solid fa-shield-check"></i>
|
||||
<span>This ticket is securely tracked and monitored by our support team</span>
|
||||
</div>
|
||||
<button
|
||||
className="btn-refresh"
|
||||
onClick={() => handleSubmit({ preventDefault: () => {} } as FormEvent<HTMLFormElement>)}
|
||||
>
|
||||
<i className="fa-solid fa-rotate"></i>
|
||||
Refresh Status
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TicketStatusCheck;
|
||||
|
||||
Reference in New Issue
Block a user