aboutsummaryrefslogtreecommitdiff
path: root/web/src/lib/api.ts
diff options
context:
space:
mode:
authorzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
committerzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
commitef55d9bb24430610b60c1b77821307cd72c08c55 (patch)
treee6935a810b34370b0a4c821e55f6397aac50c832 /web/src/lib/api.ts
downloadhelpdesk-master.tar.gz
helpdesk-master.zip
feat: upload of the projectHEADmaster
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'web/src/lib/api.ts')
-rw-r--r--web/src/lib/api.ts297
1 files changed, 297 insertions, 0 deletions
diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts
new file mode 100644
index 0000000..1d1e63d
--- /dev/null
+++ b/web/src/lib/api.ts
@@ -0,0 +1,297 @@
+/**
+ * api.ts
+ *
+ * Cliente HTTP tipado para a API Helpdesk.
+ * Centraliza toda comunicação com o backend — nenhum componente
+ * deve chamar fetch() diretamente.
+ */
+
+// ─── Configuração ─────────────────────────────────────────────────────────────
+
+const BASE_URL = import.meta.env['VITE_API_URL'] ?? '/api/v1';
+const TOKEN_KEY = 'helpdesk:token';
+
+function getToken(): string | null {
+ return localStorage.getItem(TOKEN_KEY);
+}
+
+
+// ─── Tipos (espelham o schema do backend) ─────────────────────────────────────
+
+export type TicketStatus = 'open' | 'in_progress' | 'waiting' | 'resolved' | 'closed';
+export type TicketPriority = 'low' | 'medium' | 'high' | 'critical';
+
+export interface Category {
+ id: number;
+ name: string;
+ slug: string;
+ description: string | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface Ticket {
+ id: number;
+ title: string;
+ description: string;
+ status: TicketStatus;
+ priority: TicketPriority;
+ creatorId: number;
+ assigneeId: number | null;
+ categoryId: number | null;
+ dueAt: string | null;
+ resolvedAt: string | null;
+ createdAt: string;
+ updatedAt: string;
+ creatorName?: string;
+ assigneeName?: string | null;
+ categoryName?: string | null;
+}
+
+export interface Comment {
+ id: number;
+ content: string;
+ isInternal: boolean;
+ ticketId: number;
+ authorId: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface PaginatedResponse<T> {
+ data: T[];
+ meta: {
+ page: number;
+ limit: number;
+ total: number;
+ totalPages: number;
+ };
+}
+
+export interface SystemSettings {
+ allowRequesterChat: boolean;
+}
+
+// ─── Filtros para listagem ────────────────────────────────────────────────────
+
+export interface ListTicketsParams {
+ status?: TicketStatus;
+ priority?: TicketPriority;
+ assigneeId?: number;
+ creatorId?: number;
+ categoryId?: number;
+ page?: number;
+ limit?: number;
+}
+
+// ─── Erro padronizado da API ──────────────────────────────────────────────────
+
+export class ApiError extends Error {
+ constructor(
+ public readonly status: number,
+ message: string,
+ ) {
+ super(message);
+ this.name = 'ApiError';
+ }
+}
+
+// ─── Função base de fetch ─────────────────────────────────────────────────────
+
+async function request<T>(
+ path: string,
+ options?: RequestInit,
+): Promise<T> {
+ const token = getToken();
+ const headers: Record<string, string> = { ...options?.headers as Record<string, string> };
+
+ if (options?.body && !headers['Content-Type']) {
+ headers['Content-Type'] = 'application/json';
+ }
+ if (token && !headers['Authorization']) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const response = await fetch(`${BASE_URL}${path}`, {
+ cache: 'no-store',
+ ...options,
+ headers,
+ });
+
+ if (!response.ok) {
+ const body = await response.json().catch(() => ({ message: response.statusText }));
+ throw new ApiError(
+ response.status,
+ (body as { message?: string }).message ?? 'Erro desconhecido',
+ );
+ }
+
+ if (response.status === 204) return null as T;
+
+ return response.json() as Promise<T>;
+}
+
+// ─── Tipos adicionais para Auth ───────────────────────────────────────────────
+
+export interface UserResponse {
+ id: number;
+ name: string;
+ email: string;
+ role: 'requester' | 'agent' | 'admin';
+ isActive: boolean;
+ createdAt: string;
+}
+
+export interface AuthResponse {
+ token: string;
+ user: UserResponse;
+}
+
+export interface StatsResponse {
+ totalOpen: number;
+ totalInProgress: number;
+ totalWaiting: number;
+ totalResolved: number;
+ totalClosed: number;
+ totalPending: number;
+}
+
+// ─── Endpoints: Tickets ───────────────────────────────────────────────────────
+
+export const api = {
+ settings: {
+ get(): Promise<SystemSettings> {
+ return request('/settings');
+ },
+ update(data: { allowRequesterChat: boolean }): Promise<SystemSettings> {
+ return request('/settings', { method: 'PATCH', body: JSON.stringify(data) });
+ }
+ },
+
+ stats: {
+ get(): Promise<StatsResponse> {
+ return request('/stats');
+ },
+ },
+
+ auth: {
+ login(data: { email: string; password: string }): Promise<AuthResponse> {
+ return request('/auth/login', { method: 'POST', body: JSON.stringify(data) });
+ },
+ register(data: { name: string; email: string; password: string; role?: string }): Promise<AuthResponse> {
+ return request('/auth/register', { method: 'POST', body: JSON.stringify(data) });
+ },
+ me(): Promise<UserResponse> {
+ return request('/auth/me');
+ },
+ },
+
+ categories: {
+ list(): Promise<Category[]> {
+ return request('/categories');
+ },
+ get(id: number): Promise<Category> {
+ return request(`/categories/${id}`);
+ },
+ create(data: { name: string; slug?: string; description?: string }): Promise<Category> {
+ return request('/categories', { method: 'POST', body: JSON.stringify(data) });
+ },
+ update(id: number, data: { name?: string; slug?: string; description?: string }): Promise<Category> {
+ return request(`/categories/${id}`, { method: 'PATCH', body: JSON.stringify(data) });
+ },
+ delete(id: number): Promise<void> {
+ return request(`/categories/${id}`, { method: 'DELETE' });
+ }
+ },
+
+ tickets: {
+ list(params: ListTicketsParams = {}): Promise<PaginatedResponse<Ticket>> {
+ const query = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined) query.set(key, String(value));
+ });
+ const qs = query.toString();
+ return request(`/tickets${qs ? `?${qs}` : ''}`);
+ },
+
+ get(id: number): Promise<Ticket> {
+ return request(`/tickets/${id}`);
+ },
+
+ create(data: {
+ title: string;
+ description: string;
+ priority?: TicketPriority;
+ categoryId?: number;
+ creatorId: number;
+ }): Promise<Ticket> {
+ return request('/tickets', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ },
+
+ updateStatus(ticketId: number, status: TicketStatus, reason?: string): Promise<Ticket> {
+ return request(`/tickets/${ticketId}/status`, {
+ method: 'PATCH',
+ body: JSON.stringify({ status, reason }),
+ });
+ },
+
+ assign(ticketId: number, assigneeId: number): Promise<Ticket> {
+ return request(`/tickets/${ticketId}/assign`, {
+ method: 'PATCH',
+ body: JSON.stringify({ assigneeId }),
+ });
+ },
+
+ updatePriority(ticketId: number, priority: TicketPriority): Promise<Ticket> {
+ return request(`/tickets/${ticketId}/priority`, {
+ method: 'PATCH',
+ body: JSON.stringify({ priority }),
+ });
+ },
+
+ delete(id: number): Promise<void> {
+ return request(`/tickets/${id}`, { method: 'DELETE' });
+ },
+ },
+
+ comments: {
+ list(ticketId: number): Promise<Comment[]> {
+ return request(`/tickets/${ticketId}/comments`);
+ },
+
+ add(ticketId: number, data: {
+ content: string;
+ isInternal: boolean;
+ authorId: number;
+ }): Promise<Comment> {
+ return request(`/tickets/${ticketId}/comments`, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ },
+ },
+
+ users: {
+ list(): Promise<UserResponse[]> {
+ return request('/users');
+ },
+ create(data: { name: string; email: string; password: string; role: 'admin' | 'agent' | 'requester' }): Promise<UserResponse> {
+ return request('/users', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ },
+ update(id: number, data: { name?: string; email?: string; password?: string; role?: 'admin' | 'agent' | 'requester'; isActive?: boolean }): Promise<UserResponse> {
+ return request(`/users/${id}`, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ });
+ },
+ delete(id: number): Promise<void> {
+ return request(`/users/${id}`, { method: 'DELETE' });
+ }
+ },
+};