Refactor: Use existing API for support
This commit is contained in:
@@ -1,35 +1,392 @@
|
||||
import React from 'react';
|
||||
import { Phone, MessageSquare, HeadphonesIcon } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Phone,
|
||||
MessageSquare,
|
||||
HeadphonesIcon,
|
||||
Ticket,
|
||||
BookOpen,
|
||||
BarChart3,
|
||||
Plus,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
Users,
|
||||
Star
|
||||
} from 'lucide-react';
|
||||
import { useSupport } from '@/hooks/useSupport';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
|
||||
interface SupportTabProps {
|
||||
isAdmin: boolean;
|
||||
isSuperAdmin: boolean;
|
||||
}
|
||||
|
||||
const SupportTab: React.FC<SupportTabProps> = ({ }) => {
|
||||
const SupportTab: React.FC<SupportTabProps> = ({ isAdmin, isSuperAdmin }) => {
|
||||
const {
|
||||
tickets,
|
||||
metrics,
|
||||
knowledgeBase,
|
||||
chatSessions,
|
||||
loading,
|
||||
error,
|
||||
createTicket,
|
||||
updateTicketStatus,
|
||||
clearError
|
||||
} = useSupport();
|
||||
|
||||
const [showCreateTicket, setShowCreateTicket] = useState(false);
|
||||
const [newTicket, setNewTicket] = useState({
|
||||
subject: '',
|
||||
description: '',
|
||||
priority: 'medium' as const,
|
||||
category: ''
|
||||
});
|
||||
|
||||
const handleCreateTicket = async () => {
|
||||
if (!newTicket.subject || !newTicket.description) return;
|
||||
|
||||
try {
|
||||
await createTicket({
|
||||
...newTicket,
|
||||
status: 'open',
|
||||
userId: 'current-user' // This should come from auth context
|
||||
});
|
||||
|
||||
setNewTicket({
|
||||
subject: '',
|
||||
description: '',
|
||||
priority: 'medium',
|
||||
category: ''
|
||||
});
|
||||
setShowCreateTicket(false);
|
||||
} catch (error) {
|
||||
console.error('Error creating ticket:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'urgent': return 'destructive';
|
||||
case 'high': return 'destructive';
|
||||
case 'medium': return 'default';
|
||||
case 'low': return 'secondary';
|
||||
default: return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'open': return 'destructive';
|
||||
case 'in_progress': return 'default';
|
||||
case 'resolved': return 'default';
|
||||
case 'closed': return 'secondary';
|
||||
default: return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Centro de Soporte y Tickets</h2>
|
||||
|
||||
<div className="bg-white rounded-lg shadow p-8 text-center">
|
||||
<HeadphonesIcon className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
Centro de Soporte y Tickets
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Esta sección está en desarrollo y se implementará según las especificaciones del informe.
|
||||
</p>
|
||||
<div className="text-sm text-gray-500">
|
||||
Funcionalidades pendientes:
|
||||
<ul className="mt-2 space-y-1">
|
||||
<li>• Sistema de tickets de soporte</li>
|
||||
<li>• Chat en vivo</li>
|
||||
<li>• Base de conocimientos</li>
|
||||
<li>• Métricas de soporte</li>
|
||||
<li>• Escalación automática</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-2xl font-bold text-foreground">Centro de Soporte y Tickets</h2>
|
||||
<Button onClick={() => setShowCreateTicket(true)} className="flex items-center gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
Nuevo Ticket
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<Card className="border-destructive bg-destructive/10">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-destructive">{error}</p>
|
||||
<Button variant="ghost" size="sm" onClick={clearError}>
|
||||
Cerrar
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Tabs defaultValue="overview" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Resumen</TabsTrigger>
|
||||
<TabsTrigger value="tickets">Tickets</TabsTrigger>
|
||||
<TabsTrigger value="chat">Chat en Vivo</TabsTrigger>
|
||||
<TabsTrigger value="knowledge">Base de Conocimientos</TabsTrigger>
|
||||
<TabsTrigger value="metrics">Métricas</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Tickets</CardTitle>
|
||||
<Ticket className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{metrics?.totalTickets || 0}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Tickets Abiertos</CardTitle>
|
||||
<AlertCircle className="h-4 w-4 text-destructive" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-destructive">{metrics?.openTickets || 0}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Tiempo Promedio</CardTitle>
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{metrics?.averageResponseTime || 0}h</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Satisfacción</CardTitle>
|
||||
<Star className="h-4 w-4 text-yellow-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{metrics?.customerSatisfaction || 0}/5</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tickets" className="space-y-4">
|
||||
<div className="space-y-4">
|
||||
{loading ? (
|
||||
<Card>
|
||||
<CardContent className="p-8 text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div>
|
||||
<p className="mt-2 text-muted-foreground">Cargando tickets...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : tickets.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="p-8 text-center">
|
||||
<Ticket className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">No hay tickets disponibles</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
tickets.map((ticket) => (
|
||||
<Card key={ticket.id}>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<CardTitle className="text-lg">{ticket.subject}</CardTitle>
|
||||
<CardDescription className="mt-1">
|
||||
Ticket #{ticket.id} • {new Date(ticket.createdAt).toLocaleDateString()}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Badge variant={getPriorityColor(ticket.priority)}>
|
||||
{ticket.priority}
|
||||
</Badge>
|
||||
<Badge variant={getStatusColor(ticket.status)}>
|
||||
{ticket.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground mb-4">{ticket.description}</p>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Categoría: {ticket.category}
|
||||
</span>
|
||||
{(isAdmin || isSuperAdmin) && (
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => updateTicketStatus(ticket.id, 'in_progress')}
|
||||
>
|
||||
En Progreso
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => updateTicketStatus(ticket.id, 'resolved')}
|
||||
>
|
||||
Resolver
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="chat" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<MessageSquare className="h-5 w-5" />
|
||||
Chat en Vivo
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Sistema de chat en tiempo real para soporte inmediato
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-center py-8">
|
||||
<Users className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">
|
||||
{chatSessions.length === 0
|
||||
? "No hay sesiones de chat activas"
|
||||
: `${chatSessions.length} sesiones activas`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="knowledge" className="space-y-4">
|
||||
<div className="space-y-4">
|
||||
{knowledgeBase.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="p-8 text-center">
|
||||
<BookOpen className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">No hay artículos disponibles</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
knowledgeBase.map((article) => (
|
||||
<Card key={article.id}>
|
||||
<CardHeader>
|
||||
<CardTitle>{article.title}</CardTitle>
|
||||
<CardDescription>
|
||||
Categoría: {article.category} • {article.views} vistas • {article.helpful} útiles
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground">{article.content}</p>
|
||||
<div className="flex flex-wrap gap-1 mt-4">
|
||||
{article.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="metrics" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<BarChart3 className="h-5 w-5" />
|
||||
Métricas de Soporte
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-2">Resumen de Tickets</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>Total:</span>
|
||||
<span className="font-medium">{metrics?.totalTickets || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Abiertos:</span>
|
||||
<span className="font-medium text-destructive">{metrics?.openTickets || 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Resueltos:</span>
|
||||
<span className="font-medium text-green-600">{metrics?.resolvedTickets || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-2">Rendimiento</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>Tiempo Promedio:</span>
|
||||
<span className="font-medium">{metrics?.averageResponseTime || 0} horas</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Satisfacción:</span>
|
||||
<span className="font-medium">{metrics?.customerSatisfaction || 0}/5 ⭐</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{showCreateTicket && (
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Crear Nuevo Ticket</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Input
|
||||
placeholder="Asunto del ticket"
|
||||
value={newTicket.subject}
|
||||
onChange={(e) => setNewTicket(prev => ({ ...prev, subject: e.target.value }))}
|
||||
/>
|
||||
<Textarea
|
||||
placeholder="Descripción del problema"
|
||||
value={newTicket.description}
|
||||
onChange={(e) => setNewTicket(prev => ({ ...prev, description: e.target.value }))}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Select
|
||||
value={newTicket.priority}
|
||||
onValueChange={(value: any) => setNewTicket(prev => ({ ...prev, priority: value }))}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Prioridad" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">Baja</SelectItem>
|
||||
<SelectItem value="medium">Media</SelectItem>
|
||||
<SelectItem value="high">Alta</SelectItem>
|
||||
<SelectItem value="urgent">Urgente</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
placeholder="Categoría"
|
||||
value={newTicket.category}
|
||||
onChange={(e) => setNewTicket(prev => ({ ...prev, category: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => setShowCreateTicket(false)}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={handleCreateTicket}>
|
||||
Crear Ticket
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user