diff --git a/web-ui/components/navbar.html b/web-ui/components/navbar.html
index 4f0c618..f5df8ff 100644
--- a/web-ui/components/navbar.html
+++ b/web-ui/components/navbar.html
@@ -66,65 +66,85 @@
\ No newline at end of file
diff --git a/web-ui/css/common.css b/web-ui/css/common.css
index 646aa67..eb1a24c 100644
--- a/web-ui/css/common.css
+++ b/web-ui/css/common.css
@@ -11,27 +11,34 @@
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-align: center;
- padding: 3rem 2rem;
+ padding: 2rem 1.5rem;
margin-bottom: 0;
}
.work-report-header h1 {
- font-size: 2.5rem;
+ font-size: clamp(1.5rem, 4vw, 2.5rem);
font-weight: 700;
- margin: 0 0 1rem 0;
- text-shadow: 0 2px 4px rgba(0,0,0,0.3);
+ margin: 0 0 0.75rem 0;
+ text-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.3);
+ word-wrap: break-word;
+ overflow-wrap: break-word;
}
.work-report-header .subtitle {
- font-size: 1.1rem;
+ font-size: clamp(0.875rem, 2vw, 1.1rem);
opacity: 0.9;
margin: 0;
font-weight: 300;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ max-width: 90%;
+ margin-left: auto;
+ margin-right: auto;
}
.work-report-main {
background: #f8f9fa;
- min-height: calc(100vh - 200px);
+ min-height: calc(100vh - 12rem);
padding-top: 2rem;
}
@@ -43,18 +50,52 @@
background: rgba(255, 255, 255, 0.9);
color: #495057;
text-decoration: none;
- border-radius: 8px;
+ border-radius: 0.5rem;
font-weight: 500;
- margin: 0 2rem 2rem 2rem;
+ margin: 0 1.5rem 1.5rem 1.5rem;
transition: all 0.3s ease;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.1);
+ white-space: nowrap;
}
.back-button:hover {
background: white;
color: #007bff;
- transform: translateY(-1px);
- box-shadow: 0 4px 8px rgba(0,0,0,0.15);
+ transform: translateY(-0.0625rem);
+ box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.15);
+}
+
+/* 반응형 헤더 */
+@media (max-width: 768px) {
+ .work-report-header {
+ padding: 1.5rem 1rem;
+ }
+
+ .work-report-header h1 {
+ margin-bottom: 0.5rem;
+ }
+
+ .back-button {
+ margin: 0 1rem 1rem 1rem;
+ padding: 0.625rem 1.25rem;
+ font-size: 0.875rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .work-report-header {
+ padding: 1.25rem 0.75rem;
+ }
+
+ .work-report-header .subtitle {
+ font-size: 0.8125rem;
+ }
+
+ .back-button {
+ margin: 0 0.75rem 0.75rem 0.75rem;
+ padding: 0.5rem 1rem;
+ font-size: 0.8125rem;
+ }
}
/* Reset and Base Styles */
diff --git a/web-ui/js/api-config.js b/web-ui/js/api-config.js
index 3a195bd..34aa063 100644
--- a/web-ui/js/api-config.js
+++ b/web-ui/js/api-config.js
@@ -118,18 +118,34 @@ async function apiCall(url, method = 'GET', data = null) {
if (!response.ok) {
let errorMessage = `HTTP ${response.status}`;
try {
- const errorData = await response.json();
- errorMessage = errorData.error || errorData.message || errorMessage;
- console.error('📋 서버 에러 상세:', errorData);
- } catch (e) {
- // JSON 파싱 실패시 텍스트로 시도
- try {
+ const contentType = response.headers.get('content-type');
+
+ if (contentType && contentType.includes('application/json')) {
+ const errorData = await response.json();
+ console.error('📋 서버 에러 상세:', errorData);
+
+ // 에러 메시지 추출 (여러 형식 지원)
+ if (typeof errorData === 'string') {
+ errorMessage = errorData;
+ } else if (errorData.error) {
+ errorMessage = typeof errorData.error === 'string'
+ ? errorData.error
+ : JSON.stringify(errorData.error);
+ } else if (errorData.message) {
+ errorMessage = errorData.message;
+ } else if (errorData.details) {
+ errorMessage = errorData.details;
+ } else {
+ errorMessage = `HTTP ${response.status}: ${JSON.stringify(errorData)}`;
+ }
+ } else {
const errorText = await response.text();
console.error('📋 서버 에러 텍스트:', errorText);
errorMessage = errorText || errorMessage;
- } catch (e2) {
- console.error('📋 에러 파싱 실패');
}
+ } catch (e) {
+ console.error('📋 에러 파싱 중 예외 발생:', e.message);
+ // 파싱 실패해도 HTTP 상태 코드는 전달
}
throw new Error(errorMessage);
}
@@ -182,10 +198,31 @@ async function testApiConnection() {
}
}
+// API 헬퍼 함수들
+async function apiGet(url) {
+ return apiCall(url, 'GET');
+}
+
+async function apiPost(url, data) {
+ return apiCall(url, 'POST', data);
+}
+
+async function apiPut(url, data) {
+ return apiCall(url, 'PUT', data);
+}
+
+async function apiDelete(url) {
+ return apiCall(url, 'DELETE');
+}
+
// 전역 함수로 설정
window.ensureAuthenticated = ensureAuthenticated;
window.getAuthHeaders = getAuthHeaders;
window.apiCall = apiCall;
+window.apiGet = apiGet;
+window.apiPost = apiPost;
+window.apiPut = apiPut;
+window.apiDelete = apiDelete;
window.testApiConnection = testApiConnection;
window.isTokenExpired = isTokenExpired;
window.clearAuthData = clearAuthData;
diff --git a/web-ui/js/component-loader.js b/web-ui/js/component-loader.js
index 5a5bdc8..ce45c5d 100644
--- a/web-ui/js/component-loader.js
+++ b/web-ui/js/component-loader.js
@@ -11,7 +11,7 @@ import { config } from './config.js';
export async function loadComponent(componentName, containerSelector, domProcessor = null) {
const container = document.querySelector(containerSelector);
if (!container) {
- console.error(`🔴 컴포넌트를 삽입할 컨테이너를 찾을 수 없습니다: ${containerSelector}`);
+ console.warn(`⚠️ 컴포넌트를 삽입할 컨테이너를 찾을 수 없습니다: ${containerSelector} (선택사항일 수 있음)`);
return;
}
diff --git a/web-ui/js/modules/calendar/CalendarAPI.js b/web-ui/js/modules/calendar/CalendarAPI.js
index a576248..365268b 100644
--- a/web-ui/js/modules/calendar/CalendarAPI.js
+++ b/web-ui/js/modules/calendar/CalendarAPI.js
@@ -90,7 +90,7 @@
/**
* 폴백: 순차적 로딩 (지연 시간 포함) - Private helper
- * @param {number} year
+ * @param {number} year
* @param {number} month (0-indexed)
* @returns {Promise