Files
GNX-WEB/gnx-react/components/pages/support/TicketStatusCheck.tsx
Iliyan Angelov dfcaebaf8c updates
2025-10-13 22:47:06 +03:00

324 lines
13 KiB
TypeScript

"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;