Add System Ticket
This commit is contained in:
179
frontend/src/components/Tickets.jsx
Normal file
179
frontend/src/components/Tickets.jsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { MessageSquare, Plus, Clock, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
import axios from 'axios';
|
||||
import { API_URL } from '../config';
|
||||
import TicketChat from './TicketChat';
|
||||
import CreateTicketModal from './CreateTicketModal';
|
||||
|
||||
export default function Tickets({ token, user, theme }) {
|
||||
const [tickets, setTickets] = useState([]);
|
||||
const [selectedTicket, setSelectedTicket] = useState(null);
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadTickets();
|
||||
const interval = setInterval(loadTickets, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const loadTickets = async () => {
|
||||
try {
|
||||
const { data } = await axios.get(`${API_URL}/api/tickets`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
setTickets(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки тикетов:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTicketCreated = () => {
|
||||
setShowCreateModal(false);
|
||||
loadTickets();
|
||||
};
|
||||
|
||||
const getStatusIcon = (status) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return <Clock className="w-4 h-4 text-yellow-500" />;
|
||||
case 'in_progress':
|
||||
return <AlertCircle className="w-4 h-4 text-blue-500" />;
|
||||
case 'closed':
|
||||
return <CheckCircle className="w-4 h-4 text-green-500" />;
|
||||
default:
|
||||
return <Clock className="w-4 h-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'На рассмотрении';
|
||||
case 'in_progress':
|
||||
return 'В работе';
|
||||
case 'closed':
|
||||
return 'Закрыт';
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'bg-yellow-500/20 text-yellow-500 border-yellow-500/50';
|
||||
case 'in_progress':
|
||||
return 'bg-blue-500/20 text-blue-500 border-blue-500/50';
|
||||
case 'closed':
|
||||
return 'bg-green-500/20 text-green-500 border-green-500/50';
|
||||
default:
|
||||
return 'bg-gray-500/20 text-gray-500 border-gray-500/50';
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedTicket) {
|
||||
return (
|
||||
<TicketChat
|
||||
ticket={selectedTicket}
|
||||
token={token}
|
||||
user={user}
|
||||
theme={theme}
|
||||
onBack={() => {
|
||||
setSelectedTicket(null);
|
||||
loadTickets();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`h-full ${theme.primary} ${theme.text} p-6`}>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-2">Тикеты</h1>
|
||||
<p className={theme.textSecondary}>Система поддержки</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
className={`${theme.accent} ${theme.accentHover} px-4 py-2 rounded-xl flex items-center gap-2 text-white transition`}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Создать тикет
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tickets List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
||||
<p className={theme.textSecondary}>Загрузка тикетов...</p>
|
||||
</div>
|
||||
) : tickets.length === 0 ? (
|
||||
<div className={`${theme.card} ${theme.border} border rounded-2xl p-12 text-center`}>
|
||||
<MessageSquare className={`w-16 h-16 mx-auto mb-4 ${theme.textSecondary} opacity-50`} />
|
||||
<p className="text-lg font-medium mb-2">Нет тикетов</p>
|
||||
<p className={`text-sm ${theme.textSecondary} mb-4`}>
|
||||
Создайте первый тикет для обращения в поддержку
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
className={`${theme.accent} ${theme.accentHover} px-6 py-2 rounded-xl text-white transition`}
|
||||
>
|
||||
Создать тикет
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4">
|
||||
{tickets.map((ticket) => (
|
||||
<div
|
||||
key={ticket.id}
|
||||
onClick={() => setSelectedTicket(ticket)}
|
||||
className={`${theme.card} ${theme.border} border rounded-2xl p-6 cursor-pointer ${theme.hover} transition-all duration-200`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold mb-2">{ticket.title}</h3>
|
||||
<p className={`text-sm ${theme.textSecondary} line-clamp-2`}>
|
||||
{ticket.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className={`px-3 py-1 rounded-lg border flex items-center gap-2 ${getStatusColor(ticket.status)}`}>
|
||||
{getStatusIcon(ticket.status)}
|
||||
<span className="text-sm font-medium">{getStatusText(ticket.status)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span className={theme.textSecondary}>
|
||||
Автор: <span className={theme.text}>{ticket.author}</span>
|
||||
</span>
|
||||
<span className={theme.textSecondary}>•</span>
|
||||
<span className={theme.textSecondary}>
|
||||
Сообщений: <span className={theme.text}>{ticket.messages?.length || 0}</span>
|
||||
</span>
|
||||
<span className={theme.textSecondary}>•</span>
|
||||
<span className={theme.textSecondary}>
|
||||
{new Date(ticket.created_at).toLocaleString('ru-RU')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showCreateModal && (
|
||||
<CreateTicketModal
|
||||
token={token}
|
||||
theme={theme}
|
||||
onClose={() => setShowCreateModal(false)}
|
||||
onCreated={handleTicketCreated}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user