feat: UI no-admin (vistas mis/asignados/sin-asignar, config solo admin)
This commit is contained in:
1 parent
3a1fee74e7
commit
a68999eca7
6 files changed
+124
-13
No files matched your search
@@ -8,9 +8,11 @@ import {
|
||||
deleteGroup,
|
||||
type Group,
|
||||
} from "@/lib/api";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import Modal from "@/components/Modal";
|
||||
|
||||
export default function DepartmentsPage() {
|
||||
const { user, isLoading: authLoading } = useAuth();
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
@@ -36,8 +38,13 @@ export default function DepartmentsPage() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (authLoading) return;
|
||||
if (user?.role !== "admin") {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
fetchGroups();
|
||||
}, [fetchGroups]);
|
||||
}, [fetchGroups, authLoading, user?.role]);
|
||||
|
||||
const openCreate = () => {
|
||||
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 (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
updateSla,
|
||||
type SLAPolicy,
|
||||
} from "@/lib/api";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import Modal from "@/components/Modal";
|
||||
|
||||
export default function SlaPage() {
|
||||
const { user, isLoading: authLoading } = useAuth();
|
||||
const [policies, setPolicies] = useState<SLAPolicy[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
@@ -37,8 +39,13 @@ export default function SlaPage() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (authLoading) return;
|
||||
if (user?.role !== "admin") {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
fetchPolicies();
|
||||
}, [fetchPolicies]);
|
||||
}, [fetchPolicies, authLoading, user?.role]);
|
||||
|
||||
const openCreate = () => {
|
||||
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 (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
|
||||
@@ -9,9 +9,11 @@ import {
|
||||
type TeamMember,
|
||||
type Group,
|
||||
} from "@/lib/api";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import Modal from "@/components/Modal";
|
||||
|
||||
export default function TeamPage() {
|
||||
const { user, isLoading: authLoading } = useAuth();
|
||||
const [team, setTeam] = useState<TeamMember[]>([]);
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -41,8 +43,13 @@ export default function TeamPage() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (authLoading) return;
|
||||
if (user?.role !== "admin") {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
}, [fetchData, authLoading, user?.role]);
|
||||
|
||||
const openCreate = () => {
|
||||
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 (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getTickets, type Ticket } from "@/lib/api";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import BadgeStatus from "@/components/BadgeStatus";
|
||||
import BadgePriority from "@/components/BadgePriority";
|
||||
import PaginationComponent from "@/components/Pagination";
|
||||
@@ -10,21 +11,33 @@ import Link from "next/link";
|
||||
|
||||
export default function TicketsPage() {
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const [tickets, setTickets] = useState<Ticket[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [view, setView] = useState<"all" | "mine" | "assigned" | "unassigned">("all");
|
||||
const [statusFilter, setStatusFilter] = useState("");
|
||||
const [priorityFilter, setPriorityFilter] = useState("");
|
||||
const [search, setSearch] = useState("");
|
||||
const pageSize = 10;
|
||||
|
||||
const numericId = parseInt(user?.idUsuario ?? "", 10);
|
||||
const hasNumericId = !isNaN(numericId);
|
||||
|
||||
const fetchTickets = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await getTickets({
|
||||
status: statusFilter || undefined,
|
||||
priority: priorityFilter || undefined,
|
||||
requesterId: view === "mine" && hasNumericId ? numericId : undefined,
|
||||
assigneeId:
|
||||
view === "assigned" && hasNumericId
|
||||
? numericId
|
||||
: view === "unassigned"
|
||||
? 0
|
||||
: undefined,
|
||||
search: search || undefined,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -37,7 +50,7 @@ export default function TicketsPage() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [statusFilter, priorityFilter, search, page]);
|
||||
}, [statusFilter, priorityFilter, search, page, view, numericId, hasNumericId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTickets();
|
||||
@@ -56,6 +69,30 @@ export default function TicketsPage() {
|
||||
</div>
|
||||
|
||||
{/* 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="d-flex align-items-center gap-2">
|
||||
<i className="bi bi-funnel text-muted"></i>
|
||||
|
||||
@@ -24,6 +24,9 @@ export default function Sidebar() {
|
||||
pathname === href || (href !== "/" && pathname.startsWith(href));
|
||||
|
||||
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
|
||||
?.split(" ")
|
||||
.map((n: string) => n[0])
|
||||
@@ -54,13 +57,15 @@ export default function Sidebar() {
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
<div
|
||||
className="text-uppercase mt-3 mb-1 px-3"
|
||||
style={{ fontSize: "0.7rem", letterSpacing: "0.08em", opacity: 0.6 }}
|
||||
>
|
||||
Configuración
|
||||
</div>
|
||||
{configItems.map((item) => (
|
||||
{isAdmin && (
|
||||
<>
|
||||
<div
|
||||
className="text-uppercase mt-3 mb-1 px-3"
|
||||
style={{ fontSize: "0.7rem", letterSpacing: "0.08em", opacity: 0.6 }}
|
||||
>
|
||||
Configuración
|
||||
</div>
|
||||
{configItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
@@ -69,14 +74,16 @@ export default function Sidebar() {
|
||||
<i className={`bi ${item.icon}`}></i>
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-user">
|
||||
<div className="avatar">{initials}</div>
|
||||
<div className="sidebar-user-info">
|
||||
<div className="name">{fullName}</div>
|
||||
<div className="role">{user?.tipo || "Usuario"}</div>
|
||||
<div className="role">{roleLabel}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
|
||||
@@ -44,6 +44,7 @@ export async function getTickets(params?: {
|
||||
status?: string;
|
||||
priority?: string;
|
||||
assigneeId?: number;
|
||||
requesterId?: number;
|
||||
groupId?: number;
|
||||
search?: string;
|
||||
page?: number;
|
||||
@@ -161,6 +162,9 @@ export interface HelpDeskUser {
|
||||
activo: number;
|
||||
fono?: string;
|
||||
idSede?: number;
|
||||
role?: string;
|
||||
departmentId?: number;
|
||||
departmentName?: string;
|
||||
}
|
||||
|
||||
export interface Ticket {
|
||||
|
||||
Reference in new issue
Block a user