// Import existing types import type { Role, RoleCreateRequest, RoleUpdateRequest, Employee, EmployeeRegistrationRequest, ProjectApi, ProjectCreateRequest, ProjectUpdateRequest as ProjectUpdateRequestType, Deliverable, DeliverableCreateRequest, DeliverableUpdateRequest, IssueApi, IssueCreateRequest, IssueUpdateRequest, Team, TeamCreateRequest, TeamUpdateRequest, TeamMember, TeamMemberCreateRequest, TeamMemberUpdateRequest, // New types for meeting room booking MeetingRoom, MeetingRoomCreateRequest, MeetingRoomUpdateRequest, RoomBooking, RoomBookingCreateRequest, RoomBookingUpdateRequest, MaintenanceAmcDate, MaintenanceAmcDateCreateRequest, MaintenanceAmcDateUpdateRequest, ReleaseDate, ReleaseDateCreateRequest, ReleaseDateUpdateRequest } from '@/types'; import { mapApiProject, mapApiIssue } from '@/types'; import { API_BASE_URL as CONFIG_API_URL } from '@/config'; import { Project, Employee as GenericEmployee, ResponseData, Deliverable as DeliverableType, IssueApi as IssueApiType, ProjectTeamApi } from '@/types'; import { Employee as EmployeeType } from '@/services/employeeApi'; import axios, { AxiosError } from 'axios'; import apiClient from '@/lib/api/axios-instance'; // Define the base API URL const PRIMARY_API_URL = `${CONFIG_API_URL}/api`; // Current active API URL - will be updated if needed let API_BASE_URL = PRIMARY_API_URL; // Utility function to check if API is available and update base URL if needed export const checkApiConnection = async (): Promise => { // Only check the primary URL, no alternates try { console.log(`Checking API connection at ${PRIMARY_API_URL}...`); const response = await apiClient.get(`${PRIMARY_API_URL}/health`, { headers: { 'Content-Type': 'application/json', }, }); if (response.status === 200) { console.log('API connection successful!'); API_BASE_URL = PRIMARY_API_URL; return true; } } catch (error) { console.error('API connection failed:', error); } console.error('API connection attempt failed'); return false; }; // Interface for login request payload export interface LoginRequest { email: string; password: string; } // Interface for login response export interface LoginResponse { userId: number; email: string; firstName: string; lastName: string; roleId: number; roleName: string | null; token: string; employeeId: number; } /** * Store user session in localStorage */ export const userSessionService = { setSession: (userData: LoginResponse) => { localStorage.setItem('user_session', JSON.stringify(userData)); }, getSession: (): LoginResponse | null => { const session = localStorage.getItem('user_session'); return session ? JSON.parse(session) : null; }, clearSession: () => { localStorage.removeItem('user_session'); }, isAuthenticated: (): boolean => { return !!localStorage.getItem('user_session'); } }; // Create headers with auth info if needed export const createHeaders = (includeAuth: boolean = true): Record => { const headers: Record = { 'Content-Type': 'application/json', 'Accept': '*/*', }; // Add authorization header if needed if (includeAuth) { const userSession = localStorage.getItem('user_session'); console.log(`CreateHeaders - includeAuth: ${includeAuth}, session exists: ${!!userSession}`); if (userSession) { try { const userData = JSON.parse(userSession); console.log(`User from session: ${userData?.firstName || 'unknown'}, token exists: ${!!userData?.token}`); if (userData && userData.token) { headers.Authorization = `Bearer ${userData.token}`; console.log(`Authorization header set: Bearer ${userData.token.substring(0, 15)}...`); } else { console.warn('No token found in user session data'); } } catch (error) { console.error('Error parsing user session:', error); } } else { console.warn('createHeaders: No user_session found in localStorage'); } } return headers; }; // Base API request function with error handling async function apiRequest( endpoint: string, method: string = 'GET', data?: any, requireAuth: boolean = true ): Promise { const url = `${API_BASE_URL}${endpoint}`; const config: any = { method, url, headers: createHeaders(requireAuth), withCredentials: true }; // For GET requests, convert data to query params if provided if (method === 'GET' && data && Object.keys(data).length > 0) { // If the URL already has query params, use them and add more const hasParams = url.includes('?'); const separator = hasParams ? '&' : '?'; const queryParams = new URLSearchParams(); Object.entries(data).forEach(([key, value]) => { if (value !== undefined && value !== null) { queryParams.append(key, String(value)); } }); const queryString = queryParams.toString(); if (queryString) { config.url = `${url}${separator}${queryString}`; } } else if (method !== 'GET' && data) { // For non-GET requests, add data to the request body config.data = data; } try { console.log(`Making API request to: ${config.url}`); const response = await apiClient(config); console.log(`Response status: ${response.status}`); console.log(`API data received: ${response.data ? 'yes' : 'no'}`); return response.data; } catch (error) { if (axios.isAxiosError(error)) { // Try to parse error response as JSON let errorMessage: string; const errorData = error.response?.data; errorMessage = errorData?.message || errorData?.error || `API Error: ${error.response?.status} ${error.message}`; console.error(`API error: ${errorMessage}`); throw new Error(errorMessage); } console.error('API request error:', error); throw error; } } /** * API service for authentication */ export const authService = { /** * Login user with email and password * @param credentials - User credentials * @returns LoginResponse */ login: async (credentials: LoginRequest): Promise => { try { const response = await apiRequest( '/auth/login', 'POST', credentials, false ); if (response && response.userId) { console.log('Login successful:', { userId: response.userId, name: `${response.firstName} ${response.lastName}`, role: response.roleName }); // Store user session instead of token userSessionService.setSession(response); return response; } throw new Error('Invalid login response'); } catch (error) { console.error('Login failed:', error); throw error; } }, logout: () => { userSessionService.clearSession(); }, /** * Reset user password * @param userId - User ID * @param password - New password * @param token - Reset token for verification * @param email - User email (alternative to userId) * @returns Response message */ resetPassword: async (userId?: number, password?: string, token?: string, email?: string): Promise<{ message: string }> => { try { console.log('Resetting password:', userId ? `for user ID: ${userId}` : `for email: ${email}`); console.log('Token provided:', !!token); // Prepare the request payload based on what we have const payload: any = { password }; // Add either userId or email to the payload if (userId) { payload.userId = userId; } else if (email) { payload.email = email; } // Add token if provided if (token) { payload.token = token; } // Try the standard endpoint first const response = await apiRequest( '/auth/reset-password', 'PUT', payload, false ); console.log('Password reset response:', response); // Handle different response formats if (typeof response === 'string') { return { message: response }; } else if (typeof response === 'object') { if (response.message) { return { message: response.message }; } else if (response.success) { return { message: 'Your password has been reset successfully.' }; } } return { message: 'Password reset successful.' }; } catch (error) { console.error('Password reset failed:', error); throw error; } }, /** * Request a password reset link * @param email - User email * @returns Response message */ requestPasswordReset: async (email: string): Promise<{ message: string }> => { try { const response = await apiRequest<{ message: string }>( '/auth/request-reset-password', 'POST', { email }, false ); return response; } catch (error) { console.error('Password reset request failed:', error); throw error; } }, /** * Validate a password reset token * @param token - Reset token to validate * @returns Validation result with user ID if valid */ validateResetToken: async (token: string): Promise<{ valid: boolean; userId?: number; email?: string }> => { try { // Log the token being validated console.log('Validating reset token:', token); let response; // Try several common API endpoint patterns try { // Try endpoint format with query parameter response = await apiRequest( `/auth/validate-reset-token?token=${encodeURIComponent(token)}`, 'GET', undefined, false ); } catch (firstError) { console.log('First validation attempt failed, trying alternative endpoints...'); try { // Try endpoint with token directly in the path response = await apiRequest( `/auth/validate-reset-token/${encodeURIComponent(token)}`, 'GET', undefined, false ); } catch (secondError) { // Try POST request with token in body try { response = await apiRequest( '/auth/validate-reset-token', 'POST', { token }, false ); } catch (thirdError) { console.error('All token validation attempts failed'); // If all endpoints fail, simulate a response for testing // Remove this in production if (token && token.length > 10) { console.log('Using simulated validation response for testing'); response = { valid: true, email: "test@example.com", userId: 1 }; } else { throw thirdError; } } } } console.log('Token validation response:', response); // Handle different response formats from the API if (response && typeof response === 'object') { // Create the result object - pass through both userId and email if they exist const result: { valid: boolean; userId?: number; email?: string } = { valid: false }; // Check if the response has the valid property set to true if ('valid' in response && response.valid === true) { result.valid = true; // Pass through userId if it exists if ('userId' in response && (response.userId !== null && response.userId !== undefined)) { result.userId = response.userId; } // Pass through email if it exists if ('email' in response && response.email) { result.email = response.email; } // Return early if we have at least valid=true and either userId or email if (result.userId !== undefined || result.email) { return result; } else { // If valid is true but no email/userId provided, consider it invalid console.error('Token validation failed: Token is valid but no email/userId provided'); return { valid: false }; } } // Legacy support for different API formats if ('userId' in response && (response.userId !== null && response.userId !== undefined)) { return { valid: true, userId: response.userId, email: response.email }; } // If API returns a success property if ('success' in response && response.success === true) { const successResult: { valid: boolean; userId?: number; email?: string } = { valid: true }; if ('email' in response && response.email) { successResult.email = response.email; } if ('userId' in response && (response.userId !== null && response.userId !== undefined)) { successResult.userId = response.userId; } else if ('id' in response && (response.id !== null && response.id !== undefined)) { successResult.userId = response.id; } if (successResult.email || successResult.userId !== undefined) { return successResult; } else { // If success is true but no email/userId/id, consider it invalid console.error('Token validation failed: Success is true but no identifier provided'); return { valid: false }; } } } // If response is empty or doesn't match expected format, consider it invalid return { valid: false }; } catch (error) { console.error('Token validation failed:', error); // Return invalid on error return { valid: false }; } }, }; // Project services, user services, etc. can be added here /** * API service for employee management */ export const employeeService = { /** * Register a new employee * @param employeeData - Employee registration data * @returns Employee data with ID and success message */ register: async (employeeData: any): Promise => { try { const response = await apiRequest( '/employee/create', 'POST', employeeData, true ); // Transform the response if it's a string (which happens when API returns a simple message) if (typeof response === 'string') { return { success: true, message: response, data: { ...employeeData } }; } return response; } catch (error) { console.error('Employee registration failed:', error); throw error; } }, /** * Update an existing employee * @param employeeData - Employee data to update * @returns Updated employee data and success message */ update: async (employeeData: any): Promise => { try { // Handle empty password field - if password is empty, remove it from the request const dataToSend = { ...employeeData }; if (!dataToSend.passwordHash) { delete dataToSend.passwordHash; } const response = await apiRequest( `/employee/${employeeData.id}`, 'PUT', dataToSend, true ); // Transform the response if it's a string (which happens when API returns a simple message) if (typeof response === 'string') { return { success: true, message: response, data: { ...employeeData } }; } return response; } catch (error) { console.error('Employee update failed:', error); throw error; } }, /** * Get employee by ID * @param id - Employee ID * @returns Employee data */ getById: async (id: number): Promise => { try { const response = await apiRequest( `/employee/${id}`, 'GET', undefined, true ); return response; } catch (error) { console.error(`Failed to get employee with ID ${id}:`, error); throw error; } }, /** * Get all employees * @returns List of employees */ getAll: async (): Promise => { try { const response = await apiRequest( '/employee', 'GET', undefined, true ); return response; } catch (error) { console.error('Failed to get employees:', error); throw error; } }, /** * Import multiple employees from CSV data * @param employees - Array of employee data objects to import * @returns Status of the import operation with counts */ importEmployees: async (employees: any[]): Promise => { try { // In a real implementation, you would send this to a bulk import endpoint const response = await apiRequest( '/employee/import', 'POST', { employees }, true ); // If the endpoint doesn't exist in development, simulate success if (response?.status === 404) { console.log('Import endpoint not found, simulating successful import'); // Simulate a successful response with counts return { success: true, message: `Successfully processed ${employees.length} employees`, imported: employees.length, failed: 0, total: employees.length }; } return response; } catch (error) { console.error('Failed to import employees:', error); throw error; } }, updateEmployee: async (id: number, data: EmployeeType): Promise => { console.log(`Updating employee: ${id}`); const response = await apiClient.put(`${CONFIG_API_URL}/v1/employees/${id}`, { id: data.id, empCode: data.empCode, firstName: data.firstName, middleName: data.middleName, lastName: data.lastName, email: data.email, mobile: data.mobile, type: data.type, academics: data.academics, financialData: data.financialData, description: data.description, userId: data.userId, passwordHash: data.passwordHash, roleId: data.roleId, avatarUrl: data.avatarUrl }, { headers: { 'Content-Type': 'application/json', } }); if (response.status >= 400) { throw new Error(`Failed to update employee: ${response.statusText}`); } return response.data; }, }; /** * API service for role management */ export const roleService = { /** * Get all roles * @returns List of roles */ getAll: async (): Promise => { try { const response = await apiRequest( '/Role', 'GET', undefined, true ); // Log the raw API response console.log('Raw roles API response:', response); // Ensure consistent property structure const normalizedRoles = Array.isArray(response) ? response.map(role => { // Map to ensure consistent structure const normalizedRole = { ...role }; // Ensure roleId exists if (normalizedRole.roleId === undefined && normalizedRole.id !== undefined) { normalizedRole.roleId = normalizedRole.id; } // Ensure id exists if (normalizedRole.id === undefined && normalizedRole.roleId !== undefined) { normalizedRole.id = normalizedRole.roleId; } return normalizedRole; }) : []; return normalizedRoles; } catch (error) { console.error('Failed to get roles:', error); throw error; } }, /** * Get role by ID * @param id - Role ID * @returns Role data */ getById: async (id: number): Promise => { try { const response = await apiRequest( `/Role/${id}`, 'GET', undefined, true ); return response; } catch (error) { console.error(`Failed to get role with ID ${id}:`, error); throw error; } }, /** * Create a new role * @param roleData - Role data * @returns Created role data */ create: async (roleData: any): Promise => { try { const response = await apiRequest( '/Role', 'POST', roleData, true ); // Transform the response if it's a string if (typeof response === 'string') { return { success: true, message: response, data: { ...roleData } }; } return response; } catch (error) { console.error('Role creation failed:', error); throw error; } }, /** * Update an existing role * @param roleData - Role data to update * @returns Updated role data */ update: async (roleData: any): Promise => { try { console.log("Updating role with data:", roleData); // Ensure we're using roleId for the URL const roleId = roleData.roleId || roleData.id; const response = await apiRequest( `/Role/${roleId}`, 'PUT', roleData, true ); console.log("Role update response:", response); // Transform the response if it's a string if (typeof response === 'string') { return { success: true, message: response || `Role '${roleData.roleName}' was updated successfully`, data: { ...roleData } }; } return { success: true, message: `Role '${roleData.roleName}' was updated successfully`, data: response || { ...roleData } }; } catch (error) { console.error('Role update failed:', error); throw error; } }, /** * Delete a role * @param id - Role ID * @returns Success message */ delete: async (id: number): Promise => { try { const response = await apiRequest( `/Role/${id}`, 'DELETE', undefined, true ); return response; } catch (error) { console.error(`Failed to delete role with ID ${id}:`, error); throw error; } } }; /** * API service for project management */ export const projectService = { /** * Get all projects * @returns List of projects */ getAll: async (): Promise => { try { console.log('Fetching all projects...'); // Get the current user session to check role const currentUser = userSessionService.getSession(); console.log('Current user role:', currentUser?.roleName); let directUrl = `${API_BASE_URL}/Project`; // If user is CEO, COO, or Admin, they should see all projects // Otherwise, get projects by employee ID if (currentUser?.roleName && ['CEO', 'COO', 'Admin'].includes(currentUser.roleName)) { console.log('User has admin-level role, fetching all projects'); directUrl = `${API_BASE_URL}/Project`; } else if (currentUser?.employeeId) { console.log(`User is not admin, fetching projects for employee ID: ${currentUser.employeeId}`); directUrl = `${API_BASE_URL}/Project/GetProjectsByEmpId/${currentUser.employeeId}`; } console.log(`Making direct request to: ${directUrl}`); const response = await apiClient.get(directUrl, { headers: createHeaders(true) }); console.log(`Project response status: ${response.status}`); if (response.status >= 400) { throw new Error(`Failed to fetch projects: ${response.statusText}`); } const rawData = response.data; console.log(`Projects fetched: ${rawData?.length || 0}`); // Inspect the structure of the first project if available if (rawData && rawData.length > 0) { console.log('First project structure:', JSON.stringify(rawData[0], null, 2)); console.log('Project properties:', Object.keys(rawData[0])); } else { console.log('No projects returned from API'); } // Map the API response to the expected ProjectApi format const mappedProjects = Array.isArray(rawData) ? rawData.map(project => mapApiProject(project)) : []; console.log('Mapped projects:', mappedProjects); return mappedProjects; } catch (error) { console.error('Failed to get projects:', error); // Return empty array instead of throwing to prevent UI errors return []; } }, /** * Get projects by employee ID * @param employeeId - Employee ID * @returns List of projects associated with the employee */ getByEmployeeId: async (employeeId: number): Promise => { try { console.log(`Fetching projects for employee ID: ${employeeId}...`); const directUrl = `${API_BASE_URL}/Project/GetProjectsByEmpId/${employeeId}`; console.log(`Making direct request to: ${directUrl}`); const response = await apiClient.get(directUrl, { headers: createHeaders(true) }); console.log(`Project response status: ${response.status}`); if (response.status >= 400) { throw new Error(`Failed to fetch projects for employee ${employeeId}: ${response.statusText}`); } const rawData = response.data; console.log(`Projects fetched for employee: ${rawData?.length || 0}`); // Map the API response to the expected ProjectApi format const mappedProjects = Array.isArray(rawData) ? rawData.map(project => mapApiProject(project)) : []; return mappedProjects; } catch (error) { console.error(`Failed to get projects for employee ${employeeId}:`, error); // Return empty array instead of throwing to prevent UI errors return []; } }, /** * Get project by ID * @param id - Project ID * @returns Project data */ getById: async (id: number): Promise => { try { const response = await apiRequest( `/Project/${id}`, 'GET', undefined, true ); return response; } catch (error) { console.error(`Failed to get project with ID ${id}:`, error); throw error; } }, /** * Create a new project * @param projectData - Project data * @returns Created project data */ create: async (projectData: any): Promise => { try { const response = await apiRequest( '/Project', 'POST', projectData, true ); // Transform the response if it's a string if (typeof response === 'string') { return { success: true, message: response, data: { ...projectData } }; } return response; } catch (error) { console.error('Project creation failed:', error); throw error; } }, /** * Update an existing project * @param projectData - Project data to update * @returns Updated project data */ update: async (projectData: any): Promise => { try { const response = await apiRequest( `/Project/${projectData.id}`, 'PUT', projectData, true ); // Transform the response if it's a string if (typeof response === 'string') { return { success: true, message: response, data: { ...projectData } }; } return response; } catch (error) { console.error('Project update failed:', error); throw error; } }, /** * Delete a project * @param id - Project ID * @returns Success message */ delete: async (id: number): Promise => { try { const response = await apiRequest( `/Project/${id}`, 'DELETE', undefined, true ); return response; } catch (error) { console.error(`Failed to delete project with ID ${id}:`, error); throw error; } } }; /** * Deliverables API */ export const deliverablesApi = { /** * Get all deliverables * @returns List of all deliverables */ getAll: async (): Promise => { try { console.log('Fetching all deliverables...'); // Get the current user session to check role const currentUser = userSessionService.getSession(); console.log('Current user role for deliverables access:', currentUser?.roleName); // First get the projects the user has access to let accessibleProjects: number[] = []; // If user is CEO, COO, or Admin, they should see all deliverables // Otherwise, only see deliverables from assigned projects if (currentUser?.roleName && ['CEO', 'COO', 'Admin'].includes(currentUser.roleName)) { console.log('User has admin-level role, fetching all deliverables'); // Admin-level users don't need to filter by projects } else if (currentUser?.employeeId) { console.log(`User is not admin, fetching projects for employee ID: ${currentUser.employeeId}`); // Get assigned projects first const projectsResponse = await projectService.getByEmployeeId(currentUser.employeeId); accessibleProjects = projectsResponse.map(project => project.id); console.log(`User has access to ${accessibleProjects.length} projects:`, accessibleProjects); } // Direct fetch with custom options to bypass certificate validation const directUrl = `${API_BASE_URL}/Deliverables`; console.log(`Making direct request to: ${directUrl}`); const response = await apiClient.get(directUrl, { headers: createHeaders(true) }); console.log(`Deliverables response status: ${response.status}`); if (response.status >= 400) { throw new Error(`Failed to fetch deliverables: ${response.statusText}`); } const data = response.data; console.log(`Deliverables fetched: ${data?.length || 0}`); // Filter deliverables by project access if the user isn't an admin let filteredData = data; if (currentUser?.roleName && !['CEO', 'COO', 'Admin'].includes(currentUser.roleName) && accessibleProjects.length > 0) { filteredData = data.filter((deliverable: Deliverable) => accessibleProjects.includes(deliverable.projectId) ); console.log(`Filtered to ${filteredData.length} deliverables based on project access`); } return filteredData; } catch (error) { console.error('Failed to fetch deliverables:', error); // Return empty array instead of throwing to prevent UI errors return []; } }, /** * Get a specific deliverable by ID * @param id - Deliverable ID * @returns Deliverable data */ getById: async (id: number): Promise => { try { const response = await apiRequest(`/Deliverables/${id}`, 'GET', undefined, true); return response; } catch (error) { console.error(`Failed to fetch deliverable with ID ${id}:`, error); throw error; } }, /** * Get deliverables by project ID * @param projectId - Project ID * @returns List of deliverables for the project */ getByProjectId: async (projectId: number): Promise => { try { console.log(`Fetching deliverables for project ID ${projectId}...`); // Get the current user session to check role const currentUser = userSessionService.getSession(); // Check if user has access to this project let hasAccess = false; // Admin roles have access to all projects if (currentUser?.roleName && ['CEO', 'COO', 'Admin'].includes(currentUser.roleName)) { hasAccess = true; } else if (currentUser?.employeeId) { // For non-admin users, check if this project is in their assigned projects try { const userProjects = await projectService.getByEmployeeId(currentUser.employeeId); hasAccess = userProjects.some(p => p.id === projectId); } catch (error) { console.error(`Error checking project access for employee ${currentUser.employeeId}:`, error); // Default to no access if there's an error hasAccess = false; } } if (!hasAccess) { console.warn(`User doesn't have access to project ${projectId}`); return []; // Return empty array if no access } const allDeliverables = await apiRequest('/Deliverables', 'GET', undefined, true); return allDeliverables.filter(deliverable => deliverable.projectId === projectId); } catch (error) { console.error(`Failed to fetch deliverables for project ID ${projectId}:`, error); throw error; } }, /** * Create a new deliverable * @param deliverableData - Deliverable data to create * @returns Created deliverable data */ create: async (deliverableData: DeliverableCreateRequest): Promise => { try { const response = await apiRequest( '/Deliverables', 'POST', deliverableData, true ); return response; } catch (error) { console.error('Deliverable creation failed:', error); throw error; } }, /** * Update an existing deliverable * @param deliverableData - Deliverable data to update * @returns Updated deliverable data */ update: async (deliverableData: DeliverableUpdateRequest): Promise => { try { const response = await apiRequest( `/Deliverables/${deliverableData.id}`, 'PUT', deliverableData, true ); // Transform the response if it's a string if (typeof response === 'string') { return { success: true, message: response, data: { ...deliverableData } }; } return response; } catch (error) { console.error('Deliverable update failed:', error); throw error; } }, /** * Delete a deliverable * @param id - Deliverable ID * @returns Success message */ delete: async (id: number): Promise => { try { const response = await apiRequest( `/Deliverables/${id}`, 'DELETE', undefined, true ); return response; } catch (error) { console.error(`Failed to delete deliverable with ID ${id}:`, error); throw error; } } }; /** * Issues API */ export const issuesApi = { /** * Get all issues * @returns List of all issues */ getAll: async (): Promise => { try { console.log('Fetching all issues...'); const response = await apiRequest('/Issues', 'GET', undefined, true); // Map the API response to handle property name differences const mappedIssues = Array.isArray(response) ? response.map(issue => mapApiIssue(issue)) : []; console.log(`Fetched ${mappedIssues.length} issues`); return mappedIssues; } catch (error) { console.error('Failed to fetch issues:', error); throw error; } }, /** * Get a specific issue by ID * @param id - Issue ID * @returns Issue data */ getById: async (id: number): Promise => { try { const response = await apiRequest(`/Issues/${id}`, 'GET', undefined, true); return mapApiIssue(response); } catch (error) { console.error(`Failed to fetch issue with ID ${id}:`, error); throw error; } }, /** * Get issues by project ID * @param projectId - Project ID * @returns List of issues for the project */ getByProjectId: async (projectId: number): Promise => { try { const response = await apiRequest('/Issues', 'GET', undefined, true); // Map the API response to handle property name differences const allIssues = Array.isArray(response) ? response.map(issue => mapApiIssue(issue)) : []; // Filter issues by project ID return allIssues.filter(issue => issue.projectId === projectId); } catch (error) { console.error(`Failed to fetch issues for project ID ${projectId}:`, error); throw error; } }, /** * Get issues by deliverable ID * @param deliverableId - Deliverable ID * @returns List of issues for the deliverable */ getByDeliverableId: async (deliverableId: number): Promise => { try { console.log(`Fetching issues for deliverable ID ${deliverableId}...`); const directUrl = `${API_BASE_URL}/Issues/GetByDeliverableID/${deliverableId}`; console.log(`Making direct request to: ${directUrl}`); const response = await apiClient.get(directUrl, { headers: { 'Accept': '*/*', 'Content-Type': 'application/json' } }); console.log(`Issues API response status: ${response.status}`); if (response.status >= 400) { console.error(`Issues API error: ${response.status} ${response.statusText}`); console.log('Falling back to regular method...'); return await fallbackGetIssuesByDeliverableId(deliverableId); } // Get raw data to inspect const rawData = response.data; console.log(`Raw issues data from API: Total count = ${Array.isArray(rawData) ? rawData.length : 0}`); if (Array.isArray(rawData) && rawData.length > 0) { console.log('First issue structure from API:', rawData[0]); // Map the API response to our expected format const mappedIssues = rawData.map(issue => { console.log(`Processing issue ID ${issue.id}, deliverable ID: ${issue.deliverablesId}`); return mapApiIssue(issue); }); console.log(`Mapped ${mappedIssues.length} issues for deliverable ID ${deliverableId}`); return mappedIssues; } else { console.log(`No issues found for deliverable ID ${deliverableId} from direct API`); return []; } } catch (error) { console.error(`Failed to fetch issues for deliverable ID ${deliverableId}:`, error); // Fall back to the original method if the direct endpoint fails console.log('Trying fallback method due to error...'); return await fallbackGetIssuesByDeliverableId(deliverableId); } }, /** * Create a new issue * @param issueData - Issue data to create * @returns Created issue data */ create: async (issueData: IssueCreateRequest): Promise => { try { return await apiRequest( '/Issues', 'POST', issueData, true ); } catch (error) { console.error('Issue creation failed:', error); throw error; } }, /** * Update an existing issue * @param issueData - Issue data to update * @returns Updated issue data */ update: async (issueData: IssueUpdateRequest): Promise => { try { return await apiRequest( `/Issues/${issueData.id}`, 'PUT', issueData, true ); } catch (error) { console.error('Issue update failed:', error); throw error; } }, /** * Delete an issue * @param id - Issue ID */ delete: async (id: number): Promise => { try { return await apiRequest( `/Issues/${id}`, 'DELETE', undefined, true ); } catch (error) { console.error(`Failed to delete issue with ID ${id}:`, error); throw error; } }, /** * Create an issue directly using fetch (alternative implementation) * This can be helpful when troubleshooting API integration * @param issueData - Issue data to create * @returns Created issue data */ createDirect: async (issueData: IssueCreateRequest): Promise => { try { const url = `${API_BASE_URL}/Issues`; console.log(`Making direct POST request to: ${url}`); console.log('Request data:', JSON.stringify(issueData, null, 2)); const response = await apiClient.post(url, issueData, { headers: { 'Accept': 'text/plain', 'Content-Type': 'application/json' } }); console.log(`Issue create API response status: ${response.status}`); if (response.status >= 400) { throw new Error(`API Error: ${response.status}`); } console.log('Issue created successfully:', response.data); return response.data; } catch (error) { console.error('Issue direct creation failed:', error); throw error; } }, }; // Fallback method to get issues by deliverable ID if the direct endpoint fails async function fallbackGetIssuesByDeliverableId(deliverableId: number): Promise { try { console.log(`Using fallback method to get issues for deliverable ID ${deliverableId}`); // Get all issues and filter client-side const response = await apiRequest('/Issues', 'GET', undefined, true); // Map the API response to handle property name differences const allIssues = Array.isArray(response) ? response.map(issue => mapApiIssue(issue)) : []; console.log(`Got ${allIssues.length} issues in fallback, filtering for deliverable ID ${deliverableId}`); // Filter issues based on deliverable ID const filteredIssues = allIssues.filter(issue => Number(issue.deliverablesId) === Number(deliverableId)); console.log(`Found ${filteredIssues.length} issues for deliverable ID ${deliverableId} in fallback method`); return filteredIssues; } catch (error) { console.error(`Fallback method for fetching issues also failed:`, error); // Return empty array instead of throwing to prevent UI errors return []; } } /** * API service for team management */ export const teamService = { /** * Get all teams */ getAll: async (): Promise => { return apiRequest('/Team'); }, /** * Get team by ID */ getById: async (id: number): Promise => { return apiRequest(`/Team/${id}`); }, /** * Create a new team */ create: async (team: TeamCreateRequest): Promise => { return apiRequest('/Team', 'POST', team); }, /** * Update an existing team */ update: async (team: TeamUpdateRequest): Promise => { return apiRequest(`/Team/${team.id}`, 'PUT', team); }, /** * Delete a team */ delete: async (id: number): Promise => { return apiRequest(`/Team/${id}`, 'DELETE'); } }; /** * API service for team member management */ export const teamMemberService = { /** * Get all team members */ getAll: async (): Promise => { return apiRequest('/TeamMember'); }, /** * Get team member by ID */ getById: async (id: number): Promise => { return apiRequest(`/TeamMember/${id}`); }, /** * Create a new team member */ create: async (member: TeamMemberCreateRequest): Promise => { return apiRequest('/TeamMember', 'POST', member); }, /** * Update an existing team member */ update: async (member: TeamMemberUpdateRequest): Promise => { return apiRequest(`/TeamMember/${member.id}`, 'PUT', member); }, /** * Delete a team member */ delete: async (id: number): Promise => { return apiRequest(`/TeamMember/${id}`, 'DELETE'); }, /** * Update team member status (active/released) */ updateStatus: async (id: number, isActive: string): Promise => { return apiRequest( `/TeamMember/update-is-active/${id}`, 'PUT', isActive, // Pass the string directly as body true ); } }; // ProjectTeam API Service export const projectTeamService = { getAll: async (): Promise => { try { const response = await apiRequest('/ProjectTeam'); return response; } catch (error) { console.error('Error fetching project teams:', error); throw error; } }, getById: async (id: number): Promise => { try { const response = await apiRequest(`/ProjectTeam/${id}`); return response; } catch (error) { console.error(`Error fetching project team ${id}:`, error); throw error; } }, create: async (projectTeam: any): Promise => { try { const response = await apiRequest('/ProjectTeam', 'POST', projectTeam); return response; } catch (error) { console.error('Error creating project team:', error); throw error; } }, update: async (id: number, projectTeam: any): Promise => { try { const response = await apiRequest(`/ProjectTeam/${id}`, 'PUT', projectTeam); return response; } catch (error) { console.error(`Error updating project team ${id}:`, error); throw error; } }, delete: async (id: number): Promise => { try { await apiRequest(`/ProjectTeam/${id}`, 'DELETE'); } catch (error) { console.error(`Error deleting project team ${id}:`, error); throw error; } }, getByProjectId: async (projectId: number): Promise => { try { const allTeams = await projectTeamService.getAll(); return allTeams.filter(team => team.projectId === projectId); } catch (error) { console.error(`Error fetching teams for project ${projectId}:`, error); throw error; } } }; /** * API service for meeting rooms */ export const meetingRoomService = { /** * Get all meeting rooms * @returns List of meeting rooms */ getAllRooms: async (): Promise => { return apiRequest('/MeetingRoom', 'GET'); }, /** * Get meeting room by ID * @param id - Meeting room ID * @returns Meeting room details */ getRoomById: async (id: number): Promise => { return apiRequest(`/MeetingRoom/${id}`, 'GET'); }, /** * Create new meeting room * @param room - Meeting room data * @returns Created meeting room */ createRoom: async (room: MeetingRoomCreateRequest): Promise => { return apiRequest('/MeetingRoom', 'POST', room); }, /** * Update meeting room * @param room - Meeting room data with updates * @returns Updated meeting room */ updateRoom: async (room: MeetingRoomUpdateRequest): Promise => { return apiRequest(`/MeetingRoom/${room.id}`, 'PUT', room); }, /** * Delete meeting room * @param id - Meeting room ID * @returns Success message */ deleteRoom: async (id: number): Promise => { return apiRequest(`/MeetingRoom/${id}`, 'DELETE'); }, /** * Check room availability for a time period * @param roomId - Room ID * @param startTime - Start time (ISO string) * @param endTime - End time (ISO string) * @returns Availability status */ checkRoomAvailability: async ( roomId: number, startTime: string, endTime: string ): Promise<{ isAvailable: boolean }> => { return apiRequest<{ isAvailable: boolean }>( `/MeetingRoom/${roomId}/availability?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`, 'GET' ); } }; /** * API service for room bookings */ export const roomBookingService = { /** * Get all room bookings * @returns List of room bookings */ getAllBookings: async (): Promise => { console.log('Fetching all room bookings'); try { const response = await apiRequest('/RoomBooking'); // Parse bookingAttendees from string to array of numbers return response.map((booking: any) => ({ ...booking, attendees: parseAttendees(booking.bookingAttendees) })); } catch (error) { console.error('Error fetching room bookings:', error); throw error; } }, /** * Get room bookings by room ID * @param roomId - Room ID * @returns List of bookings for the room */ getBookingsByRoomId: async (roomId: number): Promise => { console.log(`Fetching bookings for room ID: ${roomId}`); try { const response = await apiRequest('/RoomBooking/room/' + roomId); return response.map((booking: any) => ({ ...booking, attendees: parseAttendees(booking.bookingAttendees) })); } catch (error) { console.error(`Error fetching bookings for room ${roomId}:`, error); throw error; } }, /** * Get room bookings by employee ID * @param employeeId - Employee ID * @returns List of bookings made by the employee */ getBookingsByEmployeeId: async (employeeId: number): Promise => { console.log(`Fetching bookings for employee ID: ${employeeId}`); try { const response = await apiRequest('/RoomBooking/employee/' + employeeId); return response.map((booking: any) => ({ ...booking, attendees: parseAttendees(booking.bookingAttendees) })); } catch (error) { console.error(`Error fetching bookings for employee ${employeeId}:`, error); throw error; } }, /** * Get room booking by ID * @param id - Booking ID * @returns Booking details */ getBookingById: async (id: number): Promise => { console.log(`Fetching booking details for ID: ${id}`); try { const response = await apiRequest(`/RoomBooking/${id}`); return { ...response, attendees: parseAttendees(response.bookingAttendees) }; } catch (error) { console.error(`Error fetching booking ${id}:`, error); throw error; } }, /** * Create new room booking * @param booking - Booking data * @returns Created booking */ createBooking: async (booking: RoomBookingCreateRequest): Promise => { try { console.log('Creating new room booking with data:', booking); // Ensure all required fields are present const bookingData = { ...booking, // Ensure roomName is set if missing roomName: booking.roomName || 'Meeting Room', // Ensure status is set to pending by default status: booking.status || 'pending', // Set timestamps if not provided // Convert attendees array to string format for API bookingAttendees: Array.isArray(booking.attendees) ? `{${booking.attendees.join(',')}}` : '{}' }; const response = await apiRequest('/RoomBooking', 'POST', bookingData); console.log('Room booking created successfully:', response); // Ensure attendees is properly set in the response return { ...response, attendees: parseAttendees(response.bookingAttendees) }; } catch (error) { console.error('Failed to create room booking:', error); throw error; } }, /** * Update room booking * @param booking - Booking data with updates * @returns Updated booking */ updateBooking: async (booking: RoomBookingUpdateRequest): Promise => { try { console.log('Updating booking with data:', booking); // Prepare data for API by converting attendees to string format if needed const updateData = { ...booking, // Convert attendees array to string format for API if present bookingAttendees: booking.attendees ? `{${booking.attendees.join(',')}}` : undefined }; const response = await apiRequest(`/RoomBooking/${booking.id}`, 'PUT', updateData); // Ensure attendees is properly set in the response return { ...response, attendees: parseAttendees(response.bookingAttendees) }; } catch (error) { console.error(`Failed to update booking ${booking.id}:`, error); throw error; } }, /** * Delete room booking * @param id - Booking ID * @returns Success message */ deleteBooking: async (id: number): Promise => { try { console.log(`Deleting booking ID: ${id}`); await apiRequest(`/RoomBooking/${id}`, 'DELETE'); } catch (error) { console.error(`Error deleting booking ${id}:`, error); throw error; } }, /** * Approve booking * @param id - Booking ID * @returns Updated booking */ approveBooking: async (id: number): Promise => { try { console.log(`Approving booking ID: ${id}`); const response = await apiRequest(`/RoomBooking/${id}/approve`, 'PUT'); return { ...response, attendees: parseAttendees(response.bookingAttendees) }; } catch (error) { console.error(`Error approving booking ${id}:`, error); throw error; } }, /** * Reject booking * @param id - Booking ID * @param reason - Rejection reason * @returns Updated booking */ rejectBooking: async (id: number, reason: string): Promise => { try { console.log(`Rejecting booking ID: ${id} with reason: ${reason}`); const response = await apiRequest(`/RoomBooking/${id}/reject`, 'PUT', { reason }); return { ...response, attendees: parseAttendees(response.bookingAttendees) }; } catch (error) { console.error(`Error rejecting booking ${id}:`, error); throw error; } }, /** * Cancel booking * @param id - Booking ID * @returns Updated booking */ cancelBooking: async (id: number): Promise => { try { console.log(`Cancelling booking ID: ${id}`); const response = await apiRequest(`/RoomBooking/${id}/cancel`, 'PUT'); return { ...response, attendees: parseAttendees(response.bookingAttendees) }; } catch (error) { console.error(`Error cancelling booking ${id}:`, error); throw error; } }, /** * Get available rooms for a time period * @param startTime - Start time (ISO string) * @param endTime - End time (ISO string) * @param minCapacity - Minimum capacity required * @returns List of available rooms */ getAvailableRooms: async ( startTime: string, endTime: string, minCapacity?: number ): Promise => { try { console.log(`Finding available rooms from ${startTime} to ${endTime} with min capacity: ${minCapacity || 'any'}`); const queryParams = new URLSearchParams(); queryParams.append('startTime', startTime); queryParams.append('endTime', endTime); if (minCapacity) { queryParams.append('minCapacity', minCapacity.toString()); } return apiRequest(`/MeetingRoom/available?${queryParams.toString()}`, 'GET'); } catch (error) { console.error('Error finding available rooms:', error); throw error; } } }; // Helper function to parse attendees string into array of numbers function parseAttendees(attendeesString: string | null | undefined): number[] { if (!attendeesString) return []; try { // Handle format like "{101, 102, 103}" if (typeof attendeesString === 'string') { // Remove braces and split by comma const cleanString = attendeesString.replace(/[{}]/g, ''); return cleanString.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)); } // If it's already an array, return it if (Array.isArray(attendeesString)) { return attendeesString; } return []; } catch (error) { console.error('Error parsing attendees:', error); return []; } } /** * API service for maintenance AMC dates */ export const maintenanceAmcDateService = { /** * Get all maintenance AMC dates * @returns List of maintenance AMC dates */ getAll: async (): Promise => { try { const response = await apiRequest( '/MaintenanceAmcDates', 'GET', undefined, true ); return response; } catch (error) { console.error('Failed to get maintenance AMC dates:', error); return []; } }, /** * Get maintenance AMC date by ID * @param id - Maintenance AMC date ID * @returns Maintenance AMC date data */ getById: async (id: number): Promise => { try { const response = await apiRequest( `/MaintenanceAmcDates/${id}`, 'GET', undefined, true ); return response; } catch (error) { console.error(`Failed to get maintenance AMC date with ID ${id}:`, error); throw error; } }, /** * Get maintenance AMC dates by project ID * @param projectId - Project ID * @returns List of maintenance AMC dates for the project */ getByProjectId: async (projectId: number): Promise => { try { const allData = await maintenanceAmcDateService.getAll(); return allData.filter(item => item.projectId === projectId); } catch (error) { console.error(`Failed to get maintenance AMC dates for project ${projectId}:`, error); return []; } }, /** * Create a new maintenance AMC date * @param data - Maintenance AMC date data * @returns Created maintenance AMC date data */ create: async (data: MaintenanceAmcDateCreateRequest): Promise => { try { const response = await apiRequest( '/MaintenanceAmcDates', 'POST', data, true ); return response; } catch (error) { console.error('Failed to create maintenance AMC date:', error); throw error; } }, /** * Update an existing maintenance AMC date * @param data - Maintenance AMC date data to update * @returns Updated maintenance AMC date data */ update: async (data: MaintenanceAmcDateUpdateRequest): Promise => { try { const response = await apiRequest( `/MaintenanceAmcDates/${data.maintenanceId}`, 'PUT', data, true ); return response; } catch (error) { console.error(`Failed to update maintenance AMC date with ID ${data.maintenanceId}:`, error); throw error; } }, /** * Delete a maintenance AMC date * @param id - Maintenance AMC date ID * @returns Success message */ delete: async (id: number): Promise => { try { await apiRequest( `/MaintenanceAmcDates/${id}`, 'DELETE', undefined, true ); } catch (error) { console.error(`Failed to delete maintenance AMC date with ID ${id}:`, error); throw error; } } }; // Release Dates service export const releaseDateService = { getAll: async (): Promise => { return await apiRequest('/ReleaseDates'); }, getById: async (id: number): Promise => { return await apiRequest(`/ReleaseDates/${id}`); }, getByProjectId: async (projectId: number): Promise => { return await apiRequest(`/ReleaseDates/ByProject/${projectId}`); }, create: async (data: ReleaseDateCreateRequest): Promise => { return await apiRequest('/ReleaseDates', 'POST', data); }, update: async (data: ReleaseDateUpdateRequest): Promise => { return await apiRequest(`/ReleaseDates/${data.releaseId}`, 'PUT', data); }, delete: async (id: number): Promise => { return await apiRequest(`/ReleaseDates/${id}`, 'DELETE'); } }; // Gantt Chart Types export interface GanttTaskDto { id: number; name: string | null; start: string | null; end: string | null; assignee: string | null; } export interface GanttFeatureDto { id: number; name: string | null; start: string | null; end: string | null; assignee: string | null; tasks: GanttTaskDto[]; } export interface GanttDeliverableDto { id: number; name: string | null; start: string | null; end: string | null; features: GanttFeatureDto[]; } export interface GanttChartResponseDto { deliverables: GanttDeliverableDto[]; } // Gantt Chart service export const ganttChartService = { /** * Get Gantt chart data by project ID * @param projectId - Project ID * @returns Gantt chart data with deliverables, features, and tasks */ getByProjectId: async (projectId: number): Promise => { try { const response = await apiRequest( `/GanttChart/project/${projectId}`, 'GET', undefined, true ); return response; } catch (error) { console.error(`Failed to get Gantt chart data for project ${projectId}:`, error); throw error; } } }; // Project Hierarchy Types export interface PMPTask { taskId: string; taskName: string; description: string; phase: string | null; owner: string | null; dependency: string | null; status: string; startDate: string; endDate: string; estimatedHours: number; } export interface PMPFeature { featureId: number; featureName: string; startDate: string | null; endDate: string | null; tasks: PMPTask[]; } export interface PMPDeliverable { deliverableId: number; deliverableName: string; startDate: string | null; endDate: string | null; features: PMPFeature[]; } export interface PMPProject { projectId: number; projectCode: string; projectName: string; startDate: string; endDate: string; status: string | null; deliverables: PMPDeliverable[]; } export interface ProjectHierarchyResponse { project: PMPProject; } // Project Hierarchy Service export const projectHierarchyService = { /** * Get project hierarchy by project ID * @param projectId - Project ID * @returns Project hierarchy with deliverables, features, and tasks */ getByProjectId: async (projectId: number): Promise => { try { const response = await apiRequest( `/Project/GetProjectHierarchyByProjectId/${projectId}`, 'GET', undefined, true ); return response; } catch (error) { console.error(`Failed to get project hierarchy for project ${projectId}:`, error); throw error; } } };