import api from './axios-instance'; import { API_ENDPOINTS } from '@shared/constants/api-endpoints'; import type { BaseEntity } from '@shared/types/entities.types'; import type { ApiResponse, PaginatedResponse, PaginationParams } from '@shared/types/api.types'; // ============================================================================ // Interfaces // ============================================================================ export interface Project extends BaseEntity { name: string; code?: string; description?: string; partnerId?: string; partnerName?: string; userId?: string; userName?: string; teamId?: string; status: 'draft' | 'active' | 'on_hold' | 'completed' | 'cancelled'; priority: 'low' | 'medium' | 'high' | 'critical'; dateStart?: string; dateEnd?: string; plannedHours?: number; effectiveHours?: number; progress?: number; color?: string; isTemplate?: boolean; allowTimesheets: boolean; privacyVisibility: 'employees' | 'followers' | 'portal'; tags?: string[]; tenantId: string; } export interface Task extends BaseEntity { name: string; projectId: string; projectName?: string; description?: string; parentId?: string; userId?: string; userName?: string; assigneeIds?: string[]; stageId?: string; stageName?: string; status: 'new' | 'in_progress' | 'done' | 'cancelled'; priority: 'low' | 'medium' | 'high' | 'critical'; dateStart?: string; dateDeadline?: string; dateEnd?: string; plannedHours?: number; effectiveHours?: number; remainingHours?: number; progress?: number; sequence?: number; color?: string; tags?: string[]; milestoneId?: string; milestoneName?: string; tenantId: string; } export interface Timesheet extends BaseEntity { projectId: string; projectName?: string; taskId?: string; taskName?: string; userId: string; userName?: string; date: string; hours: number; description?: string; isBillable: boolean; unitAmount?: number; tenantId: string; } export interface ProjectStage extends BaseEntity { name: string; sequence: number; isFolded: boolean; projectIds?: string[]; tenantId: string; } export interface Milestone extends BaseEntity { name: string; projectId: string; projectName?: string; dateDeadline?: string; isReached: boolean; reachedDate?: string; description?: string; tenantId: string; } // ============================================================================ // DTOs // ============================================================================ export interface CreateProjectDto { name: string; code?: string; description?: string; partnerId?: string; userId?: string; teamId?: string; priority?: Project['priority']; dateStart?: string; dateEnd?: string; plannedHours?: number; color?: string; isTemplate?: boolean; allowTimesheets?: boolean; privacyVisibility?: Project['privacyVisibility']; tags?: string[]; } export interface UpdateProjectDto { name?: string; code?: string; description?: string; partnerId?: string; userId?: string; teamId?: string; status?: Project['status']; priority?: Project['priority']; dateStart?: string; dateEnd?: string; plannedHours?: number; color?: string; allowTimesheets?: boolean; privacyVisibility?: Project['privacyVisibility']; tags?: string[]; } export interface CreateTaskDto { name: string; projectId: string; description?: string; parentId?: string; userId?: string; assigneeIds?: string[]; stageId?: string; priority?: Task['priority']; dateStart?: string; dateDeadline?: string; plannedHours?: number; sequence?: number; color?: string; tags?: string[]; milestoneId?: string; } export interface UpdateTaskDto { name?: string; description?: string; parentId?: string; userId?: string; assigneeIds?: string[]; stageId?: string; status?: Task['status']; priority?: Task['priority']; dateStart?: string; dateDeadline?: string; dateEnd?: string; plannedHours?: number; sequence?: number; color?: string; tags?: string[]; milestoneId?: string; } export interface CreateTimesheetDto { projectId: string; taskId?: string; date: string; hours: number; description?: string; isBillable?: boolean; unitAmount?: number; } export interface UpdateTimesheetDto { projectId?: string; taskId?: string; date?: string; hours?: number; description?: string; isBillable?: boolean; unitAmount?: number; } export interface CreateMilestoneDto { name: string; projectId: string; dateDeadline?: string; description?: string; } export interface UpdateMilestoneDto { name?: string; dateDeadline?: string; description?: string; isReached?: boolean; } // ============================================================================ // Filters // ============================================================================ export interface ProjectFilters extends PaginationParams { status?: Project['status']; priority?: Project['priority']; partnerId?: string; userId?: string; teamId?: string; isTemplate?: boolean; dateFrom?: string; dateTo?: string; } export interface TaskFilters extends PaginationParams { projectId?: string; status?: Task['status']; priority?: Task['priority']; stageId?: string; userId?: string; assigneeId?: string; milestoneId?: string; dateFrom?: string; dateTo?: string; } export interface TimesheetFilters extends PaginationParams { projectId?: string; taskId?: string; userId?: string; isBillable?: boolean; dateFrom?: string; dateTo?: string; } // ============================================================================ // API Client // ============================================================================ export const projectsApi = { // Projects getProjects: async (filters?: ProjectFilters): Promise> => { const response = await api.get>( API_ENDPOINTS.PROJECTS.PROJECTS, { params: filters } ); return response.data; }, getProjectById: async (id: string): Promise => { const response = await api.get>( `${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Proyecto no encontrado'); } return response.data.data; }, createProject: async (data: CreateProjectDto): Promise => { const response = await api.post>( API_ENDPOINTS.PROJECTS.PROJECTS, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al crear proyecto'); } return response.data.data; }, updateProject: async (id: string, data: UpdateProjectDto): Promise => { const response = await api.patch>( `${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}`, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al actualizar proyecto'); } return response.data.data; }, archiveProject: async (id: string): Promise => { const response = await api.post>( `${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/archive` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al archivar proyecto'); } return response.data.data; }, unarchiveProject: async (id: string): Promise => { const response = await api.post>( `${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/unarchive` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al desarchivar proyecto'); } return response.data.data; }, duplicateProject: async (id: string, name?: string): Promise => { const response = await api.post>( `${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}/duplicate`, { name } ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al duplicar proyecto'); } return response.data.data; }, deleteProject: async (id: string): Promise => { const response = await api.delete( `${API_ENDPOINTS.PROJECTS.PROJECTS}/${id}` ); if (!response.data.success) { throw new Error(response.data.error || 'Error al eliminar proyecto'); } }, // Tasks getTasks: async (filters?: TaskFilters): Promise> => { const response = await api.get>( API_ENDPOINTS.PROJECTS.TASKS, { params: filters } ); return response.data; }, getTaskById: async (id: string): Promise => { const response = await api.get>( `${API_ENDPOINTS.PROJECTS.TASKS}/${id}` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Tarea no encontrada'); } return response.data.data; }, createTask: async (data: CreateTaskDto): Promise => { const response = await api.post>( API_ENDPOINTS.PROJECTS.TASKS, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al crear tarea'); } return response.data.data; }, updateTask: async (id: string, data: UpdateTaskDto): Promise => { const response = await api.patch>( `${API_ENDPOINTS.PROJECTS.TASKS}/${id}`, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al actualizar tarea'); } return response.data.data; }, moveTask: async (id: string, stageId: string, sequence?: number): Promise => { const response = await api.post>( `${API_ENDPOINTS.PROJECTS.TASKS}/${id}/move`, { stageId, sequence } ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al mover tarea'); } return response.data.data; }, deleteTask: async (id: string): Promise => { const response = await api.delete( `${API_ENDPOINTS.PROJECTS.TASKS}/${id}` ); if (!response.data.success) { throw new Error(response.data.error || 'Error al eliminar tarea'); } }, // Timesheets getTimesheets: async (filters?: TimesheetFilters): Promise> => { const response = await api.get>( API_ENDPOINTS.PROJECTS.TIMESHEETS, { params: filters } ); return response.data; }, getTimesheetById: async (id: string): Promise => { const response = await api.get>( `${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}` ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Registro de tiempo no encontrado'); } return response.data.data; }, createTimesheet: async (data: CreateTimesheetDto): Promise => { const response = await api.post>( API_ENDPOINTS.PROJECTS.TIMESHEETS, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al crear registro de tiempo'); } return response.data.data; }, updateTimesheet: async (id: string, data: UpdateTimesheetDto): Promise => { const response = await api.patch>( `${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}`, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al actualizar registro de tiempo'); } return response.data.data; }, deleteTimesheet: async (id: string): Promise => { const response = await api.delete( `${API_ENDPOINTS.PROJECTS.TIMESHEETS}/${id}` ); if (!response.data.success) { throw new Error(response.data.error || 'Error al eliminar registro de tiempo'); } }, // Stages getStages: async (): Promise => { const response = await api.get>( API_ENDPOINTS.PROJECTS.STAGES ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al obtener etapas'); } return response.data.data; }, // Milestones getMilestones: async (projectId: string): Promise => { const response = await api.get>( API_ENDPOINTS.PROJECTS.MILESTONES, { params: { projectId } } ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al obtener hitos'); } return response.data.data; }, createMilestone: async (data: CreateMilestoneDto): Promise => { const response = await api.post>( API_ENDPOINTS.PROJECTS.MILESTONES, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al crear hito'); } return response.data.data; }, updateMilestone: async (id: string, data: UpdateMilestoneDto): Promise => { const response = await api.patch>( `${API_ENDPOINTS.PROJECTS.MILESTONES}/${id}`, data ); if (!response.data.success || !response.data.data) { throw new Error(response.data.error || 'Error al actualizar hito'); } return response.data.data; }, deleteMilestone: async (id: string): Promise => { const response = await api.delete( `${API_ENDPOINTS.PROJECTS.MILESTONES}/${id}` ); if (!response.data.success) { throw new Error(response.data.error || 'Error al eliminar hito'); } }, };