Files
NeveTimePanel/frontend/src/components/Tickets.jsx
2026-01-14 21:05:22 +06:00

180 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}