Files
helpdesk/frontend/src/lib/api.ts
T

485 lines
12 KiB
TypeScript

const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000";
async function fetchApi<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const token =
typeof window !== "undefined" ? localStorage.getItem("helpdesk_token") : null;
const headers: Record<string, string> = {
"Content-Type": "application/json",
...((options.headers as Record<string, string>) || {}),
};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
if (!res.ok) {
const errorData = await res.json().catch(() => ({}));
throw new Error(errorData.message || `API Error: ${res.status}`);
}
if (res.status === 204) return undefined as T;
return res.json();
}
// ==================== Auth ====================
export async function login(idUsuario: string, password: string) {
return fetchApi<{ success: boolean; message?: string; token?: string; user?: HelpDeskUser }>(
"/api/helpdesk/login",
{ method: "POST", body: JSON.stringify({ idUsuario, password }) }
);
}
export async function getMe() {
return fetchApi<HelpDeskUser>("/api/helpdesk/me");
}
// ==================== Tickets ====================
export async function getTickets(params?: {
status?: string;
priority?: string;
assigneeId?: number;
requesterId?: number;
groupId?: number;
search?: string;
page?: number;
pageSize?: number;
}) {
const query = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== "" && value !== null)
query.set(key, String(value));
});
}
return fetchApi<TicketListResult>(`/api/helpdesk/tickets?${query.toString()}`);
}
export async function getTicket(id: number) {
return fetchApi<Ticket>(`/api/helpdesk/ticket/${id}`);
}
export async function createTicket(data: {
title: string;
description: string;
priority?: string;
assigneeId?: number;
groupId?: number;
slaPolicyId?: number;
}) {
return fetchApi<{ id: number }>("/api/helpdesk/tickets", {
method: "POST",
body: JSON.stringify(data),
});
}
export async function updateTicket(id: number, data: {
title?: string;
description?: string;
priority?: string;
assigneeId?: number;
groupId?: number;
}) {
return fetchApi<void>(`/api/helpdesk/ticket/${id}`, {
method: "PUT",
body: JSON.stringify(data),
});
}
export async function deleteTicket(id: number) {
return fetchApi<void>(`/api/helpdesk/ticket/${id}`, { method: "DELETE" });
}
export async function updateTicketStatus(id: number, status: string) {
return fetchApi<void>(`/api/helpdesk/ticket/${id}/status`, {
method: "PUT",
body: JSON.stringify({ status }),
});
}
export async function assignTicket(id: number, assigneeId: number, groupId?: number) {
return fetchApi<void>(`/api/helpdesk/ticket/${id}/assign`, {
method: "PUT",
body: JSON.stringify({ assigneeId, groupId }),
});
}
export async function getTicketStats() {
return fetchApi<DashboardStats>("/api/helpdesk/stats");
}
// ==================== Comments ====================
export async function getComments(ticketId: number) {
return fetchApi<Comment[]>(`/api/helpdesk/comments/${ticketId}`);
}
export async function addComment(
ticketId: number,
content: string,
isInternal: boolean = false
) {
return fetchApi<{ id: number }>(`/api/helpdesk/comments/${ticketId}`, {
method: "POST",
body: JSON.stringify({ content, isInternal }),
});
}
// ==================== Knowledge Base ====================
export async function getKBArticles() {
return fetchApi<KBArticle[]>("/api/helpdesk/kb/published");
}
export async function searchKB(query: string) {
return fetchApi<KBArticle[]>(`/api/helpdesk/kb/search?q=${encodeURIComponent(query)}`);
}
export async function getKBCategories() {
return fetchApi<KBCategory[]>("/api/helpdesk/kb/categories");
}
// ==================== Groups & SLA ====================
export async function getGroups() {
return fetchApi<Group[]>("/api/helpdesk/groups");
}
export async function getSLAPolicies() {
return fetchApi<SLAPolicy[]>("/api/helpdesk/sla");
}
// ==================== Types ====================
export interface HelpDeskUser {
idUsuario: string;
nombres: string;
apPaterno: string;
apMaterno?: string;
email: string;
tipo?: string;
activo: number;
fono?: string;
idSede?: number;
role?: string;
departmentId?: number;
departmentName?: string;
}
export interface Ticket {
id: number;
title: string;
description: string;
status: string;
priority: string;
ticketNumber: string;
requesterId: number;
requesterName?: string;
requesterEmail?: string;
assigneeId?: number;
assigneeName?: string;
groupId?: number;
groupName?: string;
slaPolicyId?: number;
createdAt: string;
updatedAt: string;
firstResponseAt?: string;
resolvedAt?: string;
closedAt?: string;
}
export interface TicketListResult {
items: Ticket[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
export interface Comment {
id: number;
ticketId: number;
userId: string;
userName?: string;
content: string;
isInternal: boolean;
createdAt: string;
}
export interface Group {
id: number;
name: string;
description?: string;
isActive: boolean;
}
export interface KBArticle {
id: number;
title: string;
content: string;
categoryId: number;
categoryName?: string;
authorId: string;
authorName?: string;
isPublished: boolean;
viewCount: number;
createdAt: string;
updatedAt: string;
}
export interface KBCategory {
id: number;
name: string;
description?: string;
sortOrder: number;
}
export interface SLAPolicy {
id: number;
name: string;
description?: string;
responseTimeHours: number;
resolutionTimeHours: number;
isActive: boolean;
}
export interface DashboardStats {
totalTickets: number;
openTickets: number;
inProgressTickets: number;
resolvedTickets: number;
closedTickets: number;
unassignedTickets: number;
criticalTickets: number;
dailyCounts: { date: string; count: number }[];
}
// ==================== Departamentos ====================
export async function createGroup(data: { name: string; description?: string }) {
return fetchApi<Group>("/api/helpdesk/groups", {
method: "POST",
body: JSON.stringify(data),
});
}
export async function updateGroup(
id: number,
data: { name?: string; description?: string; isActive?: boolean }
) {
return fetchApi<void>(`/api/helpdesk/groups/${id}`, {
method: "PUT",
body: JSON.stringify(data),
});
}
export async function deleteGroup(id: number) {
return fetchApi<void>(`/api/helpdesk/groups/${id}`, { method: "DELETE" });
}
// ==================== Equipo ====================
export interface TeamMember extends HelpDeskUser {
role: string;
departmentId?: number;
departmentName?: string;
isActive?: boolean;
}
export async function getTeam() {
return fetchApi<TeamMember[]>("/api/helpdesk/users");
}
export async function addMember(data: {
idUsuario: string;
role?: string;
departmentId?: number;
}) {
return fetchApi<TeamMember>("/api/helpdesk/users", {
method: "POST",
body: JSON.stringify(data),
});
}
export async function updateMember(
idUsuario: string,
data: { role?: string; departmentId?: number; isActive?: boolean }
) {
return fetchApi<TeamMember>(`/api/helpdesk/users/${encodeURIComponent(idUsuario)}`, {
method: "PUT",
body: JSON.stringify(data),
});
}
// ==================== SLA ====================
export async function createSla(data: {
name: string;
description?: string;
responseTimeHours?: number;
resolutionTimeHours?: number;
}) {
return fetchApi<SLAPolicy>("/api/helpdesk/sla", {
method: "POST",
body: JSON.stringify(data),
});
}
export async function updateSla(
id: number,
data: {
name?: string;
description?: string;
responseTimeHours?: number;
resolutionTimeHours?: number;
isActive?: boolean;
}
) {
return fetchApi<void>(`/api/helpdesk/sla/${id}`, {
method: "PUT",
body: JSON.stringify(data),
});
}
// ==================== Correo / plantillas (banco único mail.* vía /api/email/*) ====================
export interface EmailTemplate {
key: string;
name: string;
subject: string;
bodyHtml: string;
module?: string;
isActive: boolean;
}
export interface EmailLogEntry {
id: number;
template: string;
module?: string;
ticketId?: number;
recipient: string;
subject: string;
status: string;
error?: string;
createdAt: string;
}
export async function getEmailTemplates() {
return fetchApi<EmailTemplate[]>("/api/email/templates");
}
export async function createEmailTemplate(data: {
key: string;
name: string;
subject: string;
bodyHtml: string;
module?: string;
}) {
return fetchApi<{ key: string }>("/api/email/templates", {
method: "POST",
body: JSON.stringify(data),
});
}
export async function updateEmailTemplate(
key: string,
data: { subject?: string; bodyHtml?: string; isActive?: boolean }
) {
return fetchApi<void>(`/api/email/templates/${encodeURIComponent(key)}`, {
method: "PUT",
body: JSON.stringify(data),
});
}
export async function getEmailLog(limit = 50, module = "helpdesk") {
const query = new URLSearchParams({ limit: String(limit) });
if (module) query.set("module", module);
return fetchApi<EmailLogEntry[]>(`/api/email/log?${query.toString()}`);
}
// Envío vía route handler propio (/api/correos) → POST /api/email/send con
// X-Module + X-Api-Key en el servidor (la clave nunca va en el bundle).
// Reenvía el Bearer de admin para que el route verifique el rol.
export async function sendModuleEmail(data: {
to: string | string[];
templateKey?: string;
subject?: string;
htmlBody?: string;
vars?: Record<string, string | null>;
}) {
const token =
typeof window !== "undefined" ? localStorage.getItem("helpdesk_token") : null;
const res = await fetch("/api/correos", {
method: "POST",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify(data),
});
const payload = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(
(payload as { message?: string }).message || `Error de envío: ${res.status}`
);
}
return payload as { success: boolean; sent?: number; failed?: number; message?: string };
}
export async function sendTestEmail(to: string) {
return sendModuleEmail({
to,
subject: "[HelpDesk] Prueba de correo",
htmlBody: `<p>Prueba de envío desde la Mesa de Ayuda ICHN (${new Date().toISOString().slice(0, 16).replace("T", " ")} UTC).</p><p>Vía banco <code>mail.templates</code> + <code>POST /api/email/send</code> (módulo helpdesk).</p>`,
});
}
export async function syncInboundEmail() {
return fetchApi<{
success: boolean;
checkedCount: number;
inserted: number;
skipped: number;
error?: string;
}>("/api/helpdesk/email/inbound/sync", { method: "POST" });
}
// ==================== Notificaciones in-app (estándar multi-módulo) ====================
export interface AppNotification {
id: number;
module: string;
ticketId?: number;
notifRef?: string;
type: string;
title: string;
body?: string;
isRead: boolean;
createdAt: string;
}
export async function getNotifications(limit = 50, module = "", unreadOnly = false) {
const query = new URLSearchParams({ limit: String(limit) });
if (module) query.set("module", module);
if (unreadOnly) query.set("unreadOnly", "true");
return fetchApi<AppNotification[]>(`/api/notifications?${query.toString()}`);
}
export async function getUnreadCount(module = "") {
const query = new URLSearchParams();
if (module) query.set("module", module);
const qs = query.toString();
return fetchApi<{ count: number }>(`/api/notifications/unread-count${qs ? `?${qs}` : ""}`);
}
export async function markNotificationRead(id: number) {
return fetchApi<{ id: number }>(`/api/notifications/${id}/read`, {
method: "PUT",
});
}
export async function markAllNotificationsRead(module = "") {
const query = new URLSearchParams();
if (module) query.set("module", module);
const qs = query.toString();
return fetchApi<{ updated: number }>(`/api/notifications/read-all${qs ? `?${qs}` : ""}`, {
method: "PUT",
});
}