diff --git a/frontend/src/app/(app)/tickets/[id]/page.tsx b/frontend/src/app/(app)/tickets/[id]/page.tsx index 3f9cb62..e3628d7 100644 --- a/frontend/src/app/(app)/tickets/[id]/page.tsx +++ b/frontend/src/app/(app)/tickets/[id]/page.tsx @@ -8,36 +8,63 @@ import { addComment, updateTicketStatus, assignTicket, + getGroups, + getTeam, type Ticket, type Comment, + type Group, + type TeamMember, } from "@/lib/api"; import BadgeStatus from "@/components/BadgeStatus"; import BadgePriority from "@/components/BadgePriority"; import Link from "next/link"; +const STATUS_OPTIONS = [ + { value: "open", label: "Abierto" }, + { value: "pending", label: "Pendiente" }, + { value: "in_progress", label: "En Progreso" }, + { value: "resolved", label: "Resuelto" }, + { value: "closed", label: "Cerrado" }, +]; + export default function TicketDetailPage() { const params = useParams(); const id = Number(params.id); const [ticket, setTicket] = useState(null); const [comments, setComments] = useState([]); + const [groups, setGroups] = useState([]); + const [team, setTeam] = useState([]); const [loading, setLoading] = useState(true); const [comment, setComment] = useState(""); const [isInternal, setIsInternal] = useState(false); const [submitting, setSubmitting] = useState(false); + const [acting, setActing] = useState(false); + const [error, setError] = useState(""); + const [statusOpen, setStatusOpen] = useState(false); + const [assignOpen, setAssignOpen] = useState(false); useEffect(() => { Promise.all([ getTicket(id).catch(() => null), getComments(id).catch(() => []), + getGroups().catch(() => []), + getTeam().catch(() => []), ]) - .then(([ticketData, commentsData]) => { + .then(([ticketData, commentsData, groupsData, teamData]) => { setTicket(ticketData); setComments(commentsData); + setGroups(groupsData); + setTeam(teamData); }) .finally(() => setLoading(false)); }, [id]); + const refreshTicket = async () => { + const updated = await getTicket(id).catch(() => null); + if (updated) setTicket(updated); + }; + const handleAddComment = async (e: React.FormEvent) => { e.preventDefault(); if (!comment.trim()) return; @@ -56,11 +83,50 @@ export default function TicketDetailPage() { }; const handleStatusChange = async (newStatus: string) => { + setStatusOpen(false); + setError(""); + setActing(true); try { await updateTicketStatus(id, newStatus); - setTicket((prev) => (prev ? { ...prev, status: newStatus } : prev)); - } catch { - // ignore + await refreshTicket(); + } catch (err: any) { + setError(err.message || "No se pudo cambiar el estado"); + } finally { + setActing(false); + } + }; + + const handleAssignDepartment = async (groupId: number) => { + if (!ticket) return; + setAssignOpen(false); + setError(""); + setActing(true); + try { + await assignTicket(id, ticket.assigneeId ?? 0, groupId); + await refreshTicket(); + } catch (err: any) { + setError(err.message || "No se pudo asignar el departamento"); + } finally { + setActing(false); + } + }; + + const handleAssignAgent = async (idUsuario: string | null) => { + if (!ticket) return; + setAssignOpen(false); + setError(""); + setActing(true); + try { + await assignTicket( + id, + idUsuario === null ? 0 : Number(idUsuario), + ticket.groupId ?? undefined + ); + await refreshTicket(); + } catch (err: any) { + setError(err.message || "No se pudo asignar el agente"); + } finally { + setActing(false); } }; @@ -100,44 +166,111 @@ export default function TicketDetailPage() {
- {/* Status Change */} -
- -
    - {[ - { value: "Open", label: "Abierto" }, - { value: "Pending", label: "Pendiente" }, - { value: "InProgress", label: "En Progreso" }, - { value: "Resolved", label: "Resuelto" }, - { value: "Closed", label: "Cerrado" }, - ].map((s) => ( -
  • - -
  • - ))} -
+ {statusOpen && ( + <> +
setStatusOpen(false)} + /> +
    + {STATUS_OPTIONS.map((s) => ( +
  • + +
  • + ))} +
+ + )}
- {/* Assign */} -
- -
    -
  • - -
  • -
+ {assignOpen && ( + <> +
setAssignOpen(false)} + /> +
    +
  • +
    Departamento
    +
  • + {groups.length === 0 && ( +
  • Sin departamentos
  • + )} + {groups.map((g) => ( +
  • + +
  • + ))} +

  • +
  • +
    Agente
    +
  • +
  • + +
  • + {team.map((m) => ( +
  • + +
  • + ))} +
+ + )}
+ {error && ( +
+ + {error} +
+ )} +
{/* Comments Timeline */}
diff --git a/frontend/src/app/(app)/tickets/new/page.tsx b/frontend/src/app/(app)/tickets/new/page.tsx index 29eb547..191a801 100644 --- a/frontend/src/app/(app)/tickets/new/page.tsx +++ b/frontend/src/app/(app)/tickets/new/page.tsx @@ -1,8 +1,8 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import { createTicket } from "@/lib/api"; +import { createTicket, getGroups, type Group } from "@/lib/api"; import Link from "next/link"; export default function NewTicketPage() { @@ -13,13 +13,23 @@ export default function NewTicketPage() { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [priority, setPriority] = useState("Medium"); + const [groups, setGroups] = useState([]); + const [groupId, setGroupId] = useState(""); + + useEffect(() => { + getGroups().then(setGroups).catch(() => []); + }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); + if (!groupId) { + setError("Debes seleccionar un departamento"); + return; + } setLoading(true); try { - const result = await createTicket({ title, description, priority }); + const result = await createTicket({ title, description, priority, groupId: Number(groupId) }); router.push(`/tickets/${result.id}`); } catch (err: any) { setError(err.message || "Error al crear el ticket"); @@ -76,19 +86,37 @@ export default function NewTicketPage() { >
-
- - +
+
+ + +
+ +
+ + +