feat: UI no-admin (vistas mis/asignados/sin-asignar, config solo admin)

This commit is contained in:
Nurfog committed 2026-09-10 14:57:12 -03:00
1 parent 3a1fee74e7
commit a68999eca7
6 files changed
+124 -13

No files matched your search

@@ -8,9 +8,11 @@ import {
deleteGroup, deleteGroup,
type Group, type Group,
} from "@/lib/api"; } from "@/lib/api";
import { useAuth } from "@/lib/auth";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
export default function DepartmentsPage() { export default function DepartmentsPage() {
const { user, isLoading: authLoading } = useAuth();
const [groups, setGroups] = useState<Group[]>([]); const [groups, setGroups] = useState<Group[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
@@ -36,8 +38,13 @@ export default function DepartmentsPage() {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (authLoading) return;
if (user?.role !== "admin") {
setLoading(false);
return;
}
fetchGroups(); fetchGroups();
}, [fetchGroups]); }, [fetchGroups, authLoading, user?.role]);
const openCreate = () => { const openCreate = () => {
setEditing(null); setEditing(null);
@@ -94,6 +101,20 @@ export default function DepartmentsPage() {
} }
}; };
if (!authLoading && user?.role !== "admin") {
return (
<div>
<div className="page-header">
<h1>Departamentos</h1>
</div>
<div className="alert alert-warning" role="alert">
<i className="bi bi-shield-lock me-2"></i>
Solo los administradores pueden gestionar departamentos.
</div>
</div>
);
}
return ( return (
<div> <div>
<div className="page-header"> <div className="page-header">
+22 -1
View File
@@ -7,9 +7,11 @@ import {
updateSla, updateSla,
type SLAPolicy, type SLAPolicy,
} from "@/lib/api"; } from "@/lib/api";
import { useAuth } from "@/lib/auth";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
export default function SlaPage() { export default function SlaPage() {
const { user, isLoading: authLoading } = useAuth();
const [policies, setPolicies] = useState<SLAPolicy[]>([]); const [policies, setPolicies] = useState<SLAPolicy[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
@@ -37,8 +39,13 @@ export default function SlaPage() {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (authLoading) return;
if (user?.role !== "admin") {
setLoading(false);
return;
}
fetchPolicies(); fetchPolicies();
}, [fetchPolicies]); }, [fetchPolicies, authLoading, user?.role]);
const openCreate = () => { const openCreate = () => {
setEditing(null); setEditing(null);
@@ -101,6 +108,20 @@ export default function SlaPage() {
} }
}; };
if (!authLoading && user?.role !== "admin") {
return (
<div>
<div className="page-header">
<h1>Políticas SLA</h1>
</div>
<div className="alert alert-warning" role="alert">
<i className="bi bi-shield-lock me-2"></i>
Solo los administradores pueden gestionar las políticas SLA.
</div>
</div>
);
}
return ( return (
<div> <div>
<div className="page-header"> <div className="page-header">
+22 -1
View File
@@ -9,9 +9,11 @@ import {
type TeamMember, type TeamMember,
type Group, type Group,
} from "@/lib/api"; } from "@/lib/api";
import { useAuth } from "@/lib/auth";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
export default function TeamPage() { export default function TeamPage() {
const { user, isLoading: authLoading } = useAuth();
const [team, setTeam] = useState<TeamMember[]>([]); const [team, setTeam] = useState<TeamMember[]>([]);
const [groups, setGroups] = useState<Group[]>([]); const [groups, setGroups] = useState<Group[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -41,8 +43,13 @@ export default function TeamPage() {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (authLoading) return;
if (user?.role !== "admin") {
setLoading(false);
return;
}
fetchData(); fetchData();
}, [fetchData]); }, [fetchData, authLoading, user?.role]);
const openCreate = () => { const openCreate = () => {
setEditing(null); setEditing(null);
@@ -97,6 +104,20 @@ export default function TeamPage() {
} }
}; };
if (!authLoading && user?.role !== "admin") {
return (
<div>
<div className="page-header">
<h1>Equipo de Soporte</h1>
</div>
<div className="alert alert-warning" role="alert">
<i className="bi bi-shield-lock me-2"></i>
Solo los administradores pueden gestionar el equipo.
</div>
</div>
);
}
return ( return (
<div> <div>
<div className="page-header"> <div className="page-header">
+38 -1
View File
@@ -3,6 +3,7 @@
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { getTickets, type Ticket } from "@/lib/api"; import { getTickets, type Ticket } from "@/lib/api";
import { useAuth } from "@/lib/auth";
import BadgeStatus from "@/components/BadgeStatus"; import BadgeStatus from "@/components/BadgeStatus";
import BadgePriority from "@/components/BadgePriority"; import BadgePriority from "@/components/BadgePriority";
import PaginationComponent from "@/components/Pagination"; import PaginationComponent from "@/components/Pagination";
@@ -10,21 +11,33 @@ import Link from "next/link";
export default function TicketsPage() { export default function TicketsPage() {
const router = useRouter(); const router = useRouter();
const { user } = useAuth();
const [tickets, setTickets] = useState<Ticket[]>([]); const [tickets, setTickets] = useState<Ticket[]>([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [view, setView] = useState<"all" | "mine" | "assigned" | "unassigned">("all");
const [statusFilter, setStatusFilter] = useState(""); const [statusFilter, setStatusFilter] = useState("");
const [priorityFilter, setPriorityFilter] = useState(""); const [priorityFilter, setPriorityFilter] = useState("");
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const pageSize = 10; const pageSize = 10;
const numericId = parseInt(user?.idUsuario ?? "", 10);
const hasNumericId = !isNaN(numericId);
const fetchTickets = useCallback(async () => { const fetchTickets = useCallback(async () => {
setLoading(true); setLoading(true);
try { try {
const data = await getTickets({ const data = await getTickets({
status: statusFilter || undefined, status: statusFilter || undefined,
priority: priorityFilter || undefined, priority: priorityFilter || undefined,
requesterId: view === "mine" && hasNumericId ? numericId : undefined,
assigneeId:
view === "assigned" && hasNumericId
? numericId
: view === "unassigned"
? 0
: undefined,
search: search || undefined, search: search || undefined,
page, page,
pageSize, pageSize,
@@ -37,7 +50,7 @@ export default function TicketsPage() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [statusFilter, priorityFilter, search, page]); }, [statusFilter, priorityFilter, search, page, view, numericId, hasNumericId]);
useEffect(() => { useEffect(() => {
fetchTickets(); fetchTickets();
@@ -56,6 +69,30 @@ export default function TicketsPage() {
</div> </div>
{/* Filters */} {/* Filters */}
<div className="filter-bar mb-3">
<div className="btn-group" role="group" aria-label="Vista">
{(
[
["all", "Todos"],
["mine", "Míos"],
["assigned", "Asignados a mí"],
["unassigned", "Sin asignar"],
] as const
).map(([v, label]) => (
<button
key={v}
type="button"
className={`btn btn-sm ${view === v ? "btn-primary" : "btn-outline-secondary"}`}
onClick={() => {
setView(v);
setPage(1);
}}
>
{label}
</button>
))}
</div>
</div>
<div className="filter-bar mb-4"> <div className="filter-bar mb-4">
<div className="d-flex align-items-center gap-2"> <div className="d-flex align-items-center gap-2">
<i className="bi bi-funnel text-muted"></i> <i className="bi bi-funnel text-muted"></i>
+16 -9
View File
@@ -24,6 +24,9 @@ export default function Sidebar() {
pathname === href || (href !== "/" && pathname.startsWith(href)); pathname === href || (href !== "/" && pathname.startsWith(href));
const fullName = user ? `${user.nombres} ${user.apPaterno}` : "Usuario"; const fullName = user ? `${user.nombres} ${user.apPaterno}` : "Usuario";
const isAdmin = user?.role === "admin";
const roleLabel =
user?.role === "admin" ? "Administrador" : user?.role === "agent" ? "Agente" : "Usuario";
const initials = user?.nombres const initials = user?.nombres
?.split(" ") ?.split(" ")
.map((n: string) => n[0]) .map((n: string) => n[0])
@@ -54,13 +57,15 @@ export default function Sidebar() {
<span>{item.label}</span> <span>{item.label}</span>
</Link> </Link>
))} ))}
<div {isAdmin && (
className="text-uppercase mt-3 mb-1 px-3" <>
style={{ fontSize: "0.7rem", letterSpacing: "0.08em", opacity: 0.6 }} <div
> className="text-uppercase mt-3 mb-1 px-3"
Configuración style={{ fontSize: "0.7rem", letterSpacing: "0.08em", opacity: 0.6 }}
</div> >
{configItems.map((item) => ( Configuración
</div>
{configItems.map((item) => (
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
@@ -69,14 +74,16 @@ export default function Sidebar() {
<i className={`bi ${item.icon}`}></i> <i className={`bi ${item.icon}`}></i>
<span>{item.label}</span> <span>{item.label}</span>
</Link> </Link>
))} ))}
</>
)}
</nav> </nav>
<div className="sidebar-user"> <div className="sidebar-user">
<div className="avatar">{initials}</div> <div className="avatar">{initials}</div>
<div className="sidebar-user-info"> <div className="sidebar-user-info">
<div className="name">{fullName}</div> <div className="name">{fullName}</div>
<div className="role">{user?.tipo || "Usuario"}</div> <div className="role">{roleLabel}</div>
</div> </div>
<button <button
onClick={logout} onClick={logout}
+4
View File
@@ -44,6 +44,7 @@ export async function getTickets(params?: {
status?: string; status?: string;
priority?: string; priority?: string;
assigneeId?: number; assigneeId?: number;
requesterId?: number;
groupId?: number; groupId?: number;
search?: string; search?: string;
page?: number; page?: number;
@@ -161,6 +162,9 @@ export interface HelpDeskUser {
activo: number; activo: number;
fono?: string; fono?: string;
idSede?: number; idSede?: number;
role?: string;
departmentId?: number;
departmentName?: string;
} }
export interface Ticket { export interface Ticket {