🚀 Release v1.4.1 - Enhanced Ticket Management Features
✨ New Features: • Added admin filter by client/organization in internal frontend • Enhanced attachments viewer with toggle button in client frontend • Added attachment counter and improved UX in ticket conversation 🔧 Backend Improvements: • New `/tickets/admin/all` endpoint for administrators • Advanced filtering by tenant, status, and priority • Rich response data with tenant and user information • Proper admin permissions validation 🎨 Frontend Enhancements: • Client filter dropdown in admin panel • Enhanced ticket table with client/organization info • Collapsible attachments section with visual indicator • Improved API parameter handling (fixed undefined filters) • Better responsive design and hover effects 🐛 Bug Fixes: • Fixed undefined URL parameters in API calls • Corrected parameter filtering in API utility • Improved error handling for admin endpoints 📱 UI/UX: • Added visual badges for attachment count • Enhanced table columns with client/creator information • Improved button styling and interaction feedback • Better organization of ticket conversation layout
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user