feat: botones Cambiar Estado y Asignar funcionales + departamento al crear ticket

This commit is contained in:
Nurfog committed 2026-09-14 13:09:53 -03:00
1 parent 5c36382600
commit 85fcabd5a5
2 files changed
+209 -48

No files matched your search

+165 -32
View File
@@ -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<Ticket | null>(null);
const [comments, setComments] = useState<Comment[]>([]);
const [groups, setGroups] = useState<Group[]>([]);
const [team, setTeam] = useState<TeamMember[]>([]);
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() {
</div>
</div>
<div className="d-flex gap-2">
{/* Status Change */}
<div className="dropdown">
<button className="btn btn-outline-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown">
{/* Status Change (dropdown controlado por React: sin bootstrap.js) */}
<div className="position-relative">
<button
className="btn btn-outline-primary btn-sm"
disabled={acting}
onClick={() => {
setStatusOpen((o) => !o);
setAssignOpen(false);
}}
>
<i className="bi bi-arrow-repeat me-1"></i>Cambiar Estado
</button>
<ul className="dropdown-menu">
{[
{ value: "Open", label: "Abierto" },
{ value: "Pending", label: "Pendiente" },
{ value: "InProgress", label: "En Progreso" },
{ value: "Resolved", label: "Resuelto" },
{ value: "Closed", label: "Cerrado" },
].map((s) => (
<li key={s.value}>
<button className="dropdown-item" onClick={() => handleStatusChange(s.value)}>
{s.label}
</button>
</li>
))}
</ul>
{statusOpen && (
<>
<div
className="position-fixed top-0 start-0 w-100 h-100"
style={{ zIndex: 1000 }}
onClick={() => setStatusOpen(false)}
/>
<ul className="dropdown-menu show position-absolute end-0" style={{ zIndex: 1001 }}>
{STATUS_OPTIONS.map((s) => (
<li key={s.value}>
<button
className={`dropdown-item ${ticket.status === s.value ? "active" : ""}`}
onClick={() => handleStatusChange(s.value)}
>
{s.label}
</button>
</li>
))}
</ul>
</>
)}
</div>
{/* Assign */}
<div className="dropdown">
<button className="btn btn-outline-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown">
{/* Assign (dropdown controlado por React: sin bootstrap.js) */}
<div className="position-relative">
<button
className="btn btn-outline-primary btn-sm"
disabled={acting}
onClick={() => {
setAssignOpen((o) => !o);
setStatusOpen(false);
}}
>
<i className="bi bi-person-plus me-1"></i>Asignar
</button>
<ul className="dropdown-menu">
<li>
<button className="dropdown-item" onClick={() => assignTicket(id, 0)}>
Sin asignar
</button>
</li>
</ul>
{assignOpen && (
<>
<div
className="position-fixed top-0 start-0 w-100 h-100"
style={{ zIndex: 1000 }}
onClick={() => setAssignOpen(false)}
/>
<ul className="dropdown-menu show position-absolute end-0" style={{ zIndex: 1001, minWidth: "220px" }}>
<li>
<h6 className="dropdown-header">Departamento</h6>
</li>
{groups.length === 0 && (
<li><span className="dropdown-item-text text-muted">Sin departamentos</span></li>
)}
{groups.map((g) => (
<li key={g.id}>
<button
className={`dropdown-item ${ticket.groupId === g.id ? "active" : ""}`}
onClick={() => handleAssignDepartment(g.id)}
>
<i className="bi bi-diagram-3 me-2"></i>{g.name}
</button>
</li>
))}
<li><hr className="dropdown-divider" /></li>
<li>
<h6 className="dropdown-header">Agente</h6>
</li>
<li>
<button className="dropdown-item" onClick={() => handleAssignAgent(null)}>
<i className="bi bi-person-dash me-2"></i>Sin asignar
</button>
</li>
{team.map((m) => (
<li key={m.idUsuario}>
<button
className="dropdown-item"
onClick={() => handleAssignAgent(m.idUsuario)}
>
<i className="bi bi-person me-2"></i>
{m.nombres} {m.apPaterno}
</button>
</li>
))}
</ul>
</>
)}
</div>
</div>
</div>
{error && (
<div className="alert alert-danger py-2" role="alert">
<i className="bi bi-exclamation-circle me-2"></i>
{error}
</div>
)}
<div className="row g-4">
{/* Comments Timeline */}
<div className="col-lg-8">