Add System Ticket

This commit is contained in:
2026-01-14 21:05:22 +06:00
parent f0a4ad177e
commit cf131bb04e
3 changed files with 513 additions and 0 deletions

View 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>
);
}