diff --git a/backend/app/api/v1/endpoints/tickets.py b/backend/app/api/v1/endpoints/tickets.py index f380ab5..1ed5df4 100644 --- a/backend/app/api/v1/endpoints/tickets.py +++ b/backend/app/api/v1/endpoints/tickets.py @@ -210,6 +210,148 @@ async def get_tickets( ] +@router.get("/admin/all", response_model=List[dict]) +async def get_all_tickets_admin( + skip: int = 0, + limit: int = 100, + status_filter: Optional[str] = None, + priority_filter: Optional[str] = None, + tenant_id_filter: Optional[str] = None, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """ + Obtener todos los tickets de todos los tenants (solo para administradores) + Incluye información del tenant y usuario que creó el ticket + """ + # Verificar que el usuario sea administrador + if current_user.role not in ["ADMIN", "SUPPORT_MANAGER"]: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="No tienes permisos para acceder a esta función" + ) + + # Query base con joins para obtener información del tenant y usuario creador + query = select(Ticket, Tenant, User).join( + Tenant, Ticket.tenant_id == Tenant.id + ).join( + User, Ticket.created_by == User.id + ) + + # Aplicar filtros + if status_filter: + try: + status_enum = TicketStatus[status_filter.upper()] + query = query.where(Ticket.status == status_enum) + except KeyError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid status: {status_filter}" + ) + + if priority_filter: + try: + priority_enum = TicketPriority[priority_filter.upper()] + query = query.where(Ticket.priority == priority_enum) + except KeyError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid priority: {priority_filter}" + ) + + if tenant_id_filter: + try: + tenant_uuid = uuid.UUID(tenant_id_filter) + query = query.where(Ticket.tenant_id == tenant_uuid) + except ValueError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid tenant ID format" + ) + + query = query.order_by(Ticket.created_at.desc()).offset(skip).limit(limit) + + result = await db.execute(query) + rows = result.all() + + return [ + { + "id": str(ticket.id), + "ticket_number": ticket.ticket_number, + "subject": ticket.subject, + "title": ticket.subject, + "description": ticket.description, + "status": ticket.status.value, + "priority": ticket.priority.value, + "category_id": str(ticket.category_id) if ticket.category_id else None, + "affected_system_id": str(ticket.affected_system_id) if ticket.affected_system_id else None, + "created_by": str(ticket.created_by), + "assigned_to": str(ticket.assigned_to) if ticket.assigned_to else None, + "created_at": ticket.created_at, + "updated_at": ticket.updated_at, + # Información del tenant/cliente + "tenant": { + "id": str(tenant.id), + "name": tenant.name, + "slug": tenant.slug, + "contact_email": tenant.contact_email + }, + # Información del usuario creador + "created_by_user": { + "id": str(user.id), + "email": user.email, + "first_name": user.first_name, + "last_name": user.last_name, + "role": user.role.value if hasattr(user.role, 'value') else str(user.role) + } + } + for ticket, tenant, user in rows + ] + + +@router.get("/{ticket_id}", response_model=TicketResponse) +async def get_ticket( + ticket_id: str, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user) +): + + if status_filter: + try: + status_enum = TicketStatus[status_filter.upper()] + query = query.where(Ticket.status == status_enum) + except KeyError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid status: {status_filter}" + ) + + query = query.order_by(Ticket.created_at.desc()).offset(skip).limit(limit) + + result = await db.execute(query) + tickets = result.scalars().all() + + # ✅ CORREGIDO: Usar affected_system_id + return [ + { + "id": str(t.id), + "ticket_number": t.ticket_number, + "subject": t.subject, + "title": t.subject, + "description": t.description, + "status": t.status.value, + "priority": t.priority.value, + "category_id": str(t.category_id) if t.category_id else None, + "affected_system_id": str(t.affected_system_id) if t.affected_system_id else None, # ✅ CORREGIDO + "created_by": str(t.created_by), + "assigned_to": str(t.assigned_to) if t.assigned_to else None, + "created_at": t.created_at, + "updated_at": t.updated_at + } + for t in tickets + ] + + @router.get("/{ticket_id}", response_model=TicketResponse) async def get_ticket( ticket_id: str, diff --git a/frontend-client/src/routes/tickets/[id]/+page.svelte b/frontend-client/src/routes/tickets/[id]/+page.svelte index 02114cd..18de429 100644 --- a/frontend-client/src/routes/tickets/[id]/+page.svelte +++ b/frontend-client/src/routes/tickets/[id]/+page.svelte @@ -14,7 +14,8 @@ let closeResolution = ''; let fileInput: HTMLInputElement; let isUploading = false; - let pollingInterval: any = null; + let pollingInterval: any = null; + let showAttachments = true; // Variable para controlar la visibilidad de attachments async function loadComments() { try { @@ -257,42 +258,59 @@ {#if $tickets.attachments.length > 0}
- {attachment.original_filename} -
-- {Math.round(attachment.size_bytes / 1024)} KB • - Subido por {attachment.uploaded_by_name} • - {formatDate(attachment.uploaded_at)} -
-+ {attachment.original_filename} +
++ {Math.round(attachment.size_bytes / 1024)} KB • + Subido por {attachment.uploaded_by_name} • + {formatDate(attachment.uploaded_at)} +
+