// API 기본 설정 const API_BASE_URL = '/api'; // 토큰 관리 const TokenManager = { getToken: () => localStorage.getItem('access_token'), setToken: (token) => localStorage.setItem('access_token', token), removeToken: () => localStorage.removeItem('access_token'), getUser: () => { const userStr = localStorage.getItem('current_user'); return userStr ? JSON.parse(userStr) : null; }, setUser: (user) => localStorage.setItem('current_user', JSON.stringify(user)), removeUser: () => localStorage.removeItem('current_user') }; // API 요청 헬퍼 async function apiRequest(endpoint, options = {}) { const token = TokenManager.getToken(); const defaultHeaders = { 'Content-Type': 'application/json', }; if (token) { defaultHeaders['Authorization'] = `Bearer ${token}`; } const config = { ...options, headers: { ...defaultHeaders, ...options.headers } }; try { const response = await fetch(`${API_BASE_URL}${endpoint}`, config); if (response.status === 401) { // 인증 실패 시 로그인 페이지로 TokenManager.removeToken(); TokenManager.removeUser(); window.location.href = '/index.html'; return; } if (!response.ok) { const error = await response.json(); console.error('API Error Response:', error); console.error('Error details:', JSON.stringify(error, null, 2)); // 422 에러의 경우 validation 에러 메시지 추출 if (response.status === 422 && error.detail && Array.isArray(error.detail)) { const validationErrors = error.detail.map(err => `${err.loc ? err.loc.join('.') : 'field'}: ${err.msg}` ).join(', '); throw new Error(`입력값 검증 오류: ${validationErrors}`); } throw new Error(error.detail || 'API 요청 실패'); } return await response.json(); } catch (error) { console.error('API 요청 에러:', error); throw error; } } // Auth API const AuthAPI = { login: async (username, password) => { const formData = new URLSearchParams(); formData.append('username', username); formData.append('password', password); const response = await fetch(`${API_BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData.toString() }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || '로그인 실패'); } const data = await response.json(); TokenManager.setToken(data.access_token); TokenManager.setUser(data.user); return data; }, logout: () => { TokenManager.removeToken(); TokenManager.removeUser(); window.location.href = '/index.html'; }, getMe: () => apiRequest('/auth/me'), createUser: (userData) => apiRequest('/auth/users', { method: 'POST', body: JSON.stringify(userData) }), getUsers: () => apiRequest('/auth/users'), updateUser: (userId, userData) => apiRequest(`/auth/users/${userId}`, { method: 'PUT', body: JSON.stringify(userData) }), deleteUser: (userId) => apiRequest(`/auth/users/${userId}`, { method: 'DELETE' }), changePassword: (currentPassword, newPassword) => apiRequest('/auth/change-password', { method: 'POST', body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }) }), resetPassword: (userId, newPassword = '000000') => apiRequest(`/auth/users/${userId}`, { method: 'PUT', body: JSON.stringify({ password: newPassword }) }) }; // Issues API const IssuesAPI = { create: async (issueData) => { // photos 배열 처리 (최대 2장) const dataToSend = { category: issueData.category, description: issueData.description, project_id: issueData.project_id, photo: issueData.photos && issueData.photos.length > 0 ? issueData.photos[0] : null, photo2: issueData.photos && issueData.photos.length > 1 ? issueData.photos[1] : null }; return apiRequest('/issues/', { method: 'POST', body: JSON.stringify(dataToSend) }); }, getAll: (params = {}) => { const queryString = new URLSearchParams(params).toString(); return apiRequest(`/issues/${queryString ? '?' + queryString : ''}`); }, get: (id) => apiRequest(`/issues/${id}`), update: (id, issueData) => apiRequest(`/issues/${id}`, { method: 'PUT', body: JSON.stringify(issueData) }), delete: (id) => apiRequest(`/issues/${id}`, { method: 'DELETE' }), getStats: () => apiRequest('/issues/stats/summary') }; // Daily Work API const DailyWorkAPI = { create: (workData) => apiRequest('/daily-work/', { method: 'POST', body: JSON.stringify(workData) }), getAll: (params = {}) => { const queryString = new URLSearchParams(params).toString(); return apiRequest(`/daily-work/${queryString ? '?' + queryString : ''}`); }, get: (id) => apiRequest(`/daily-work/${id}`), update: (id, workData) => apiRequest(`/daily-work/${id}`, { method: 'PUT', body: JSON.stringify(workData) }), delete: (id) => apiRequest(`/daily-work/${id}`, { method: 'DELETE' }), getStats: (params = {}) => { const queryString = new URLSearchParams(params).toString(); return apiRequest(`/daily-work/stats/summary${queryString ? '?' + queryString : ''}`); } }; // Reports API const ReportsAPI = { getSummary: (startDate, endDate) => apiRequest('/reports/summary', { method: 'POST', body: JSON.stringify({ start_date: startDate, end_date: endDate }) }), getIssues: (startDate, endDate) => { const params = new URLSearchParams({ start_date: startDate, end_date: endDate }).toString(); return apiRequest(`/reports/issues?${params}`); }, getDailyWorks: (startDate, endDate) => { const params = new URLSearchParams({ start_date: startDate, end_date: endDate }).toString(); return apiRequest(`/reports/daily-works?${params}`); } }; // 권한 체크 function checkAuth() { const user = TokenManager.getUser(); if (!user) { window.location.href = '/index.html'; return null; } return user; } function checkAdminAuth() { const user = checkAuth(); if (user && user.role !== 'admin') { alert('관리자 권한이 필요합니다.'); window.location.href = '/index.html'; return null; } return user; } // 프로젝트 API const ProjectsAPI = { getAll: (activeOnly = false) => { const params = activeOnly ? '?active_only=true' : ''; return apiRequest(`/projects${params}`); }, get: (id) => apiRequest(`/projects/${id}`), create: (projectData) => apiRequest('/projects', { method: 'POST', body: JSON.stringify(projectData) }), update: (id, projectData) => apiRequest(`/projects/${id}`, { method: 'PUT', body: JSON.stringify(projectData) }), delete: (id) => apiRequest(`/projects/${id}`, { method: 'DELETE' }) };