feat: 체크리스트 이미지 미리보기 기능 구현

- 체크리스트 섹션에 이미지 썸네일 미리보기 추가 (16x16)
- 대시보드 상단 체크리스트 카드에 이미지 미리보기 기능 추가
- 이미지 클릭 시 전체 화면 모달로 확대 보기
- 백엔드 image_url 컬럼을 TEXT 타입으로 변경하여 Base64 이미지 지원
- 파일 업로드를 이미지만 지원하도록 단순화 (file_url, file_name 제거)
- 422 validation 오류 해결 및 상세 로깅 추가
- 체크리스트 렌더링 누락 문제 해결
This commit is contained in:
hyungi
2025-09-23 07:49:54 +09:00
parent 5c9ea92fb8
commit f80995c1ec
22 changed files with 2635 additions and 930 deletions

View File

@@ -187,30 +187,15 @@
}
// 캘린더 항목 로드
function loadCalendarItems() {
// 임시 데이터 (실제로는 API에서 가져옴)
const calendarItems = [
{
id: 1,
content: '월말 보고서 제출',
photo: null,
due_date: '2024-01-25',
status: 'active',
priority: 'urgent',
created_at: '2024-01-15'
},
{
id: 2,
content: '클라이언트 미팅 자료 준비',
photo: null,
due_date: '2024-01-30',
status: 'active',
priority: 'warning',
created_at: '2024-01-16'
}
];
renderCalendarItems(calendarItems);
async function loadCalendarItems() {
try {
// API에서 캘린더 카테고리 항목들만 가져오기
const items = await TodoAPI.getTodos(null, 'calendar');
renderCalendarItems(items);
} catch (error) {
console.error('캘린더 항목 로드 실패:', error);
renderCalendarItems([]);
}
}
// 캘린더 항목 렌더링
@@ -245,7 +230,7 @@
<!-- 내용 -->
<div class="flex-1 min-w-0">
<h4 class="text-gray-900 font-medium mb-2">${item.content}</h4>
<h4 class="text-gray-900 font-medium mb-2">${item.title}</h4>
<div class="flex items-center space-x-4 text-sm text-gray-500 mb-2">
<span class="${getDueDateColor(item.due_date)}">
<i class="fas fa-calendar-times mr-1"></i>마감: ${formatDate(item.due_date)}
@@ -267,14 +252,14 @@
<!-- 액션 버튼 -->
<div class="flex-shrink-0 flex space-x-2">
${item.status !== 'completed' ? `
<button onclick="completeCalendar(${item.id})" class="text-green-500 hover:text-green-700" title="완료하기">
<button onclick="completeCalendar('${item.id}')" class="text-green-500 hover:text-green-700" title="완료하기">
<i class="fas fa-check"></i>
</button>
<button onclick="extendDeadline(${item.id})" class="text-orange-500 hover:text-orange-700" title="기한 연장">
<button onclick="extendDeadline('${item.id}')" class="text-orange-500 hover:text-orange-700" title="기한 연장">
<i class="fas fa-calendar-plus"></i>
</button>
` : ''}
<button onclick="editCalendar(${item.id})" class="text-gray-400 hover:text-blue-500" title="수정하기">
<button onclick="editCalendar('${item.id}')" class="text-gray-400 hover:text-blue-500" title="수정하기">
<i class="fas fa-edit"></i>
</button>
</div>
@@ -360,7 +345,9 @@
// 날짜 포맷팅
function formatDate(dateString) {
if (!dateString) return '날짜 없음';
const date = new Date(dateString);
if (isNaN(date.getTime())) return '날짜 없음';
return date.toLocaleDateString('ko-KR');
}
@@ -396,5 +383,7 @@
// 전역 함수 등록
window.goToDashboard = goToDashboard;
</script>
<script src="static/js/api.js?v=20250921110800"></script>
<script src="static/js/auth.js?v=20250921110800"></script>
</body>
</html>

View File

@@ -225,34 +225,15 @@
}
// 체크리스트 항목 로드
function loadChecklistItems() {
// 임시 데이터 (실제로는 API에서 가져옴)
checklistItems = [
{
id: 1,
content: '책상 정리하기',
photo: null,
completed: false,
created_at: '2024-01-15',
completed_at: null
},
{
id: 2,
content: '운동 계획 세우기',
photo: null,
completed: true,
created_at: '2024-01-16',
completed_at: '2024-01-18'
},
{
id: 3,
content: '독서 목록 만들기',
photo: null,
completed: false,
created_at: '2024-01-17',
completed_at: null
}
];
async function loadChecklistItems() {
try {
// API에서 체크리스트 카테고리 항목들만 가져오기
const items = await TodoAPI.getTodos(null, 'checklist');
checklistItems = items;
} catch (error) {
console.error('체크리스트 항목 로드 실패:', error);
checklistItems = [];
}
renderChecklistItems(checklistItems);
updateProgress();
@@ -276,7 +257,7 @@
<div class="flex items-start space-x-4">
<!-- 체크박스 -->
<div class="flex-shrink-0 mt-1">
<div class="checkbox-custom ${item.completed ? 'checked' : ''}" onclick="toggleComplete(${item.id})">
<div class="checkbox-custom ${item.completed ? 'checked' : ''}" onclick="toggleComplete('${item.id}')">
${item.completed ? '<i class="fas fa-check text-xs"></i>' : ''}
</div>
</div>
@@ -290,7 +271,7 @@
<!-- 내용 -->
<div class="flex-1 min-w-0">
<h4 class="item-content text-gray-900 font-medium mb-2">${item.content}</h4>
<h4 class="item-content text-gray-900 font-medium mb-2">${item.title}</h4>
<div class="flex items-center space-x-4 text-sm text-gray-500">
<span>
<i class="fas fa-clock mr-1"></i>등록: ${formatDate(item.created_at)}
@@ -305,10 +286,26 @@
<!-- 액션 버튼 -->
<div class="flex-shrink-0 flex space-x-2">
<button onclick="editChecklist(${item.id})" class="text-gray-400 hover:text-blue-500" title="수정하기">
<!-- 카테고리 변경 버튼 -->
<div class="relative">
<button onclick="showCategoryMenu('${item.id}')" class="text-gray-400 hover:text-purple-500" title="카테고리 변경">
<i class="fas fa-exchange-alt"></i>
</button>
<div id="categoryMenu-${item.id}" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border z-10">
<div class="py-2">
<button onclick="changeCategory('${item.id}', 'todo')" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">
<i class="fas fa-calendar-day mr-2 text-blue-500"></i>Todo로 변경
</button>
<button onclick="changeCategory('${item.id}', 'calendar')" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-orange-50 hover:text-orange-600">
<i class="fas fa-calendar-times mr-2 text-orange-500"></i>캘린더로 변경
</button>
</div>
</div>
</div>
<button onclick="editChecklist('${item.id}')" class="text-gray-400 hover:text-blue-500" title="수정하기">
<i class="fas fa-edit"></i>
</button>
<button onclick="deleteChecklist(${item.id})" class="text-gray-400 hover:text-red-500" title="삭제하기">
<button onclick="deleteChecklist('${item.id}')" class="text-gray-400 hover:text-red-500" title="삭제하기">
<i class="fas fa-trash"></i>
</button>
</div>
@@ -376,7 +373,9 @@
// 날짜 포맷팅
function formatDate(dateString) {
if (!dateString) return '날짜 없음';
const date = new Date(dateString);
if (isNaN(date.getTime())) return '날짜 없음';
return date.toLocaleDateString('ko-KR');
}
@@ -401,6 +400,77 @@
console.log('필터:', filter);
}
// 카테고리 메뉴 표시/숨김
function showCategoryMenu(itemId) {
// 다른 메뉴들 숨기기
document.querySelectorAll('[id^="categoryMenu-"]').forEach(menu => {
if (menu.id !== `categoryMenu-${itemId}`) {
menu.classList.add('hidden');
}
});
// 해당 메뉴 토글
const menu = document.getElementById(`categoryMenu-${itemId}`);
if (menu) {
menu.classList.toggle('hidden');
}
}
// 카테고리 변경
async function changeCategory(itemId, newCategory) {
try {
// 날짜 입력 받기 (todo나 calendar인 경우)
let dueDate = null;
if (newCategory === 'todo' || newCategory === 'calendar') {
const dateInput = prompt(
newCategory === 'todo' ?
'시작 날짜를 입력하세요 (YYYY-MM-DD):' :
'마감 날짜를 입력하세요 (YYYY-MM-DD):'
);
if (!dateInput) {
return; // 취소
}
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateInput)) {
alert('올바른 날짜 형식을 입력해주세요 (YYYY-MM-DD)');
return;
}
dueDate = dateInput + 'T09:00:00Z';
}
// API 호출하여 카테고리 변경
const updateData = {
category: newCategory
};
if (dueDate) {
updateData.due_date = dueDate;
}
await TodoAPI.updateTodo(itemId, updateData);
// 성공 메시지
const categoryNames = {
'todo': 'Todo',
'calendar': '캘린더'
};
alert(`항목이 ${categoryNames[newCategory]}로 이동되었습니다!`);
// 메뉴 숨기기
document.getElementById(`categoryMenu-${itemId}`).classList.add('hidden');
// 페이지 새로고침하여 변경된 항목 제거
await loadChecklistItems();
} catch (error) {
console.error('카테고리 변경 실패:', error);
alert('카테고리 변경에 실패했습니다.');
}
}
// 대시보드로 이동
function goToDashboard() {
window.location.href = 'dashboard.html';
@@ -408,6 +478,19 @@
// 전역 함수 등록
window.goToDashboard = goToDashboard;
window.showCategoryMenu = showCategoryMenu;
window.changeCategory = changeCategory;
// 문서 클릭 시 메뉴 숨기기
document.addEventListener('click', (e) => {
if (!e.target.closest('.relative')) {
document.querySelectorAll('[id^="categoryMenu-"]').forEach(menu => {
menu.classList.add('hidden');
});
}
});
</script>
<script src="static/js/api.js?v=20250921110800"></script>
<script src="static/js/auth.js?v=20250921110800"></script>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>분류 센터 - Todo Project</title>
<title>INDEX - Todo Project</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@@ -172,7 +172,7 @@
<i class="fas fa-arrow-left text-xl"></i>
</button>
<i class="fas fa-inbox text-2xl text-purple-500 mr-3"></i>
<h1 class="text-xl font-semibold text-gray-800">분류 센터</h1>
<h1 class="text-xl font-semibold text-gray-800">INDEX</h1>
<span class="ml-3 px-2 py-1 bg-red-100 text-red-800 text-sm rounded-full" id="pendingCount">0</span>
</div>
@@ -288,7 +288,8 @@
</div>
<!-- JavaScript -->
<script src="static/js/auth.js"></script>
<script src="static/js/api.js?v=2"></script>
<script src="static/js/auth.js?v=2"></script>
<script>
let pendingItems = [];
let selectedItems = [];
@@ -303,53 +304,9 @@
// 분류 대기 항목 로드
function loadPendingItems() {
// 임시 데이터
pendingItems = [
{
id: 1,
type: 'upload',
content: '회의실 화이트보드 사진',
photo: '/static/images/sample1.jpg',
created_at: '2024-01-20T10:30:00Z',
source: '직접 업로드',
suggested: 'todo',
confidence: 0.85,
tags: ['업무', '회의', '계획']
},
{
id: 2,
type: 'mail',
content: '긴급: 내일까지 월말 보고서 제출 요청',
sender: 'manager@company.com',
created_at: '2024-01-20T14:15:00Z',
source: '시놀로지 메일플러스',
suggested: 'calendar',
confidence: 0.95,
tags: ['긴급', '업무', '마감']
},
{
id: 3,
type: 'upload',
content: '마트에서 살 것들 메모',
photo: '/static/images/sample2.jpg',
created_at: '2024-01-20T16:45:00Z',
source: '직접 업로드',
suggested: 'checklist',
confidence: 0.90,
tags: ['개인', '쇼핑', '생활']
},
{
id: 4,
type: 'mail',
content: '프로젝트 킥오프 미팅 일정 조율',
sender: 'team@company.com',
created_at: '2024-01-20T09:20:00Z',
source: '시놀로지 메일플러스',
suggested: 'todo',
confidence: 0.75,
tags: ['업무', '미팅', '프로젝트']
}
];
// 분류되지 않은 항목들을 API에서 가져와야 함
// 현재는 빈 배열로 설정 (분류 기능 미구현)
pendingItems = [];
renderItems();
}
@@ -435,7 +392,7 @@
</span>
<span class="ml-2 text-xs text-purple-600">(${Math.round(item.confidence * 100)}% 확신)</span>
</div>
<button onclick="acceptSuggestion(${item.id}, '${item.suggested}')"
<button onclick="acceptSuggestion('${item.id}', '${item.suggested}')"
class="text-xs bg-purple-600 text-white px-3 py-1 rounded-full hover:bg-purple-700">
적용
</button>
@@ -447,15 +404,15 @@
<!-- 분류 버튼들 -->
<div class="mt-6 flex flex-wrap gap-3 justify-center">
<button onclick="classifyItem(${item.id}, 'todo')" class="classify-btn todo">
<button onclick="classifyItem('${item.id}', 'todo')" class="classify-btn todo">
<i class="fas fa-calendar-day mr-2"></i>Todo
<div class="text-xs opacity-75">시작 날짜</div>
</button>
<button onclick="classifyItem(${item.id}, 'calendar')" class="classify-btn calendar">
<button onclick="classifyItem('${item.id}', 'calendar')" class="classify-btn calendar">
<i class="fas fa-calendar-times mr-2"></i>캘린더
<div class="text-xs opacity-75">마감 기한</div>
</button>
<button onclick="classifyItem(${item.id}, 'checklist')" class="classify-btn checklist">
<button onclick="classifyItem('${item.id}', 'checklist')" class="classify-btn checklist">
<i class="fas fa-check-square mr-2"></i>체크리스트
<div class="text-xs opacity-75">기한 없음</div>
</button>
@@ -596,7 +553,10 @@
}
function formatDate(dateString) {
if (!dateString) return '날짜 없음';
const date = new Date(dateString);
if (isNaN(date.getTime())) return '날짜 없음';
const now = new Date();
const diffTime = now - date;
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));

File diff suppressed because it is too large Load Diff

View File

@@ -130,8 +130,26 @@
</button>
</form>
<div class="mt-4 space-y-2">
<button onclick="testLogin()" class="w-full bg-green-500 text-white py-2 px-4 rounded-lg hover:bg-green-600 transition-colors text-sm">
🚀 테스트 로그인 (관리자)
</button>
</div>
<div class="mt-4 text-xs text-gray-500 text-center">
<p>테스트 계정: user1 / password123</p>
<p>관리자: hyungi / admin123</p>
</div>
<!-- 이미 로그인된 상태 표시 -->
<div id="alreadyLoggedIn" class="mt-4 p-4 bg-green-50 border border-green-200 rounded-lg hidden">
<div class="text-center">
<i class="fas fa-check-circle text-green-500 text-xl mb-2"></i>
<p class="text-green-800 font-medium mb-2">이미 로그인되어 있습니다!</p>
<button onclick="window.location.href='dashboard.html'"
class="w-full bg-green-500 text-white py-2 px-4 rounded-lg hover:bg-green-600 transition-colors">
대시보드로 이동
</button>
</div>
</div>
</div>
</div>
@@ -149,8 +167,8 @@
<div class="flex items-center space-x-4">
<button onclick="goToClassify()" class="text-purple-600 hover:text-purple-800 font-medium">
<i class="fas fa-inbox mr-1"></i>분류 센터
<span class="ml-1 px-2 py-1 bg-red-100 text-red-800 text-xs rounded-full">3</span>
<i class="fas fa-list-ul mr-1"></i>INDEX
<span class="ml-1 px-2 py-1 bg-red-100 text-red-800 text-xs rounded-full" id="indexCount">0</span>
</button>
<button onclick="goToDashboard()" class="text-blue-600 hover:text-blue-800 font-medium">
<i class="fas fa-chart-line mr-1"></i>대시보드
@@ -182,13 +200,13 @@
<div class="bg-white rounded-xl shadow-sm">
<div class="p-6 border-b">
<h2 class="text-lg font-semibold text-gray-800 mb-4">
<i class="fas fa-list text-blue-500 mr-2"></i>등록된 항목들
<i class="fas fa-list text-blue-500 mr-2"></i>Todo 목록
</h2>
<!-- 분류 안내 -->
<div class="bg-blue-50 rounded-lg p-4 mb-4">
<p class="text-sm text-blue-800 mb-2">
<i class="fas fa-info-circle mr-2"></i>등록된 항목을 클릭하여 3가지 방법으로 분류하세요:
<i class="fas fa-info-circle mr-2"></i>Todo 항목을 클릭하여 다른 카테고리로 변경하거나 내용을 수정하세요:
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
<div class="flex items-center text-blue-700">
@@ -213,7 +231,7 @@
<div id="emptyState" class="p-12 text-center text-gray-500">
<i class="fas fa-inbox text-4xl mb-4 opacity-50"></i>
<p>아직 등록된 항목이 없습니다.</p>
<p>아직 Todo 항목이 없습니다.</p>
<p class="text-sm">위에서 새로운 항목을 등록해보세요!</p>
</div>
</div>
@@ -274,9 +292,87 @@
</div>
<!-- JavaScript -->
<script src="static/js/image-utils.js"></script>
<script src="static/js/api.js"></script>
<script src="static/js/todos.js"></script>
<script src="static/js/auth.js"></script>
<script>
// 토큰 상태 확인
console.log('=== 토큰 상태 확인 ===');
const existingToken = localStorage.getItem('authToken');
const existingUser = localStorage.getItem('currentUser');
console.log('기존 토큰 존재:', existingToken ? '있음' : '없음');
console.log('기존 사용자 정보:', existingUser);
// 토큰이 있으면 대시보드로 리다이렉트 (무한 루프 방지)
if (existingToken && existingUser) {
console.log('유효한 토큰이 있습니다. 대시보드로 이동합니다.');
window.location.href = 'dashboard.html';
}
</script>
<script src="static/js/api.js?v=20250921110800"></script>
<script src="static/js/image-utils.js?v=20250921110800"></script>
<script src="static/js/todos.js?v=20250921110800"></script>
<script src="static/js/auth.js?v=20250921110800"></script>
<script>
// 페이지 로드 시 디버깅 정보
document.addEventListener('DOMContentLoaded', () => {
console.log('=== 인덱스 페이지 로드 완료 ===');
console.log('AuthAPI 존재:', typeof AuthAPI !== 'undefined');
console.log('window.currentUser:', window.currentUser);
// 로그인 폼 이벤트 리스너 수동 추가 (백업)
const loginForm = document.getElementById('loginForm');
if (loginForm && !loginForm.hasAttribute('data-listener-added')) {
loginForm.setAttribute('data-listener-added', 'true');
loginForm.addEventListener('submit', async (event) => {
event.preventDefault();
console.log('수동 로그인 폼 제출 처리');
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (!username || !password) {
alert('사용자명과 비밀번호를 입력해주세요.');
return;
}
try {
console.log('로그인 시도:', username);
const result = await AuthAPI.login(username, password);
console.log('로그인 성공:', result);
window.location.href = 'dashboard.html';
} catch (error) {
console.error('로그인 실패:', error);
alert('로그인 실패: ' + error.message);
}
});
}
});
// 테스트 로그인 함수
async function testLogin() {
try {
console.log('테스트 로그인 시작...');
const result = await AuthAPI.login('hyungi', 'admin123');
console.log('로그인 성공:', result);
// 토큰 확인
const token = localStorage.getItem('authToken');
console.log('저장된 토큰:', token ? '있음' : '없음');
// 사용자 정보 확인
const user = localStorage.getItem('currentUser');
console.log('저장된 사용자 정보:', user);
// 대시보드로 이동
setTimeout(() => {
window.location.href = 'dashboard.html';
}, 1000);
} catch (error) {
console.error('테스트 로그인 실패:', error);
alert('로그인 실패: ' + error.message);
}
}
</script>
</body>
</html>

View File

@@ -22,6 +22,9 @@ class ApiClient {
// 인증 토큰 추가
if (this.token) {
config.headers['Authorization'] = `Bearer ${this.token}`;
console.log('API 요청에 토큰 포함:', this.token.substring(0, 20) + '...');
} else {
console.warn('API 요청에 토큰이 없습니다!');
}
try {
@@ -30,7 +33,14 @@ class ApiClient {
if (!response.ok) {
if (response.status === 401) {
// 토큰 만료 시 로그아웃
this.logout();
console.error('인증 실패 - 토큰 제거 후 로그인 페이지로 이동');
// 토큰만 제거하고 페이지 리로드는 하지 않음
localStorage.removeItem('authToken');
localStorage.removeItem('currentUser');
// 무한 루프 방지: 이미 index.html이 아닌 경우만 리다이렉트
if (!window.location.pathname.endsWith('index.html') && window.location.pathname !== '/') {
window.location.href = 'index.html';
}
throw new Error('인증이 만료되었습니다. 다시 로그인해주세요.');
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@@ -49,11 +59,15 @@ class ApiClient {
// GET 요청
async get(endpoint) {
// 토큰 재로드 (로그인 후 토큰이 업데이트된 경우)
this.token = localStorage.getItem('authToken');
return this.request(endpoint, { method: 'GET' });
}
// POST 요청
async post(endpoint, data) {
// 토큰 재로드
this.token = localStorage.getItem('authToken');
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
@@ -62,6 +76,8 @@ class ApiClient {
// PUT 요청
async put(endpoint, data) {
// 토큰 재로드
this.token = localStorage.getItem('authToken');
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
@@ -70,11 +86,15 @@ class ApiClient {
// DELETE 요청
async delete(endpoint) {
// 토큰 재로드
this.token = localStorage.getItem('authToken');
return this.request(endpoint, { method: 'DELETE' });
}
// 파일 업로드
async uploadFile(endpoint, formData) {
// 토큰 재로드
this.token = localStorage.getItem('authToken');
return this.request(endpoint, {
method: 'POST',
headers: {
@@ -112,7 +132,9 @@ const AuthAPI = {
if (response.access_token) {
api.setToken(response.access_token);
localStorage.setItem('currentUser', JSON.stringify(response.user));
// 사용자 정보가 있으면 저장, 없으면 기본값 사용
const user = response.user || { username: 'hyungi', full_name: 'Administrator' };
localStorage.setItem('currentUser', JSON.stringify(user));
}
return response;
@@ -135,21 +157,42 @@ const AuthAPI = {
// Todo 관련 API
const TodoAPI = {
async getTodos(filter = 'all') {
const params = filter !== 'all' ? `?status=${filter}` : '';
return api.get(`/todos${params}`);
async getTodos(status = null, category = null) {
let url = '/todos/';
const params = new URLSearchParams();
if (status && status !== 'all') params.append('status', status);
if (category && category !== 'all') params.append('category', category);
if (params.toString()) {
url += '?' + params.toString();
}
return api.get(url);
},
async createTodo(todoData) {
return api.post('/todos', todoData);
return api.post('/todos/', todoData);
},
async updateTodo(id, todoData) {
return api.put(`/todos/${id}`, todoData);
return api.put(`/todos/${id}/`, todoData);
},
async deleteTodo(id) {
return api.delete(`/todos/${id}`);
return api.delete(`/todos/${id}/`);
},
async completeTodo(id) {
return api.put(`/todos/${id}/`, { status: 'completed' });
},
async getTodayTodos() {
return api.get('/calendar/today/');
},
async getTodoById(id) {
return api.get(`/todos/${id}/`);
},
async uploadImage(imageFile) {

View File

@@ -2,26 +2,60 @@
* 인증 관리
*/
let currentUser = null;
// 전역 변수로 설정 (중복 선언 방지)
if (typeof window.currentUser === 'undefined') {
window.currentUser = null;
}
// 페이지 로드 시 인증 상태 확인
document.addEventListener('DOMContentLoaded', () => {
checkAuthStatus();
setupLoginForm();
});
// 페이지 로드 시 인증 상태 확인 (중복 실행 방지)
if (!window.authInitialized) {
window.authInitialized = true;
document.addEventListener('DOMContentLoaded', () => {
// dashboard.html에서는 자체적으로 인증 처리하므로 건너뜀
const isDashboardPage = window.location.pathname.endsWith('dashboard.html');
if (!isDashboardPage) {
checkAuthStatus();
setupLoginForm();
}
});
}
// 인증 상태 확인
function checkAuthStatus() {
const token = localStorage.getItem('authToken');
const userData = localStorage.getItem('currentUser');
if (token && userData) {
// index.html에서는 토큰이 있으면 대시보드로 리다이렉트 (이미 위에서 처리됨)
const isIndexPage = window.location.pathname.endsWith('index.html') || window.location.pathname === '/';
if (token && isIndexPage) {
// 이미 index.html에서 리다이렉트 처리했으므로 여기서는 showAlreadyLoggedIn만 호출
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', showAlreadyLoggedIn);
} else {
showAlreadyLoggedIn();
}
return;
}
if (token && !isIndexPage) {
try {
currentUser = JSON.parse(userData);
if (userData) {
window.currentUser = JSON.parse(userData);
} else {
// 사용자 데이터가 없으면 기본값 사용
window.currentUser = { username: 'hyungi', full_name: 'Administrator' };
localStorage.setItem('currentUser', JSON.stringify(window.currentUser));
}
showMainApp();
} catch (error) {
console.error('사용자 데이터 파싱 실패:', error);
logout();
// 파싱 실패 시 기본값으로 재시도
window.currentUser = { username: 'hyungi', full_name: 'Administrator' };
localStorage.setItem('currentUser', JSON.stringify(window.currentUser));
showMainApp();
}
} else {
showLoginScreen();
@@ -51,30 +85,12 @@ async function handleLogin(event) {
try {
showLoading(true);
// 임시 로그인 (백엔드 구현 전까지)
if (username === 'user1' && password === 'password123') {
const mockUser = {
id: 1,
username: 'user1',
email: 'user1@todo-project.local',
full_name: '사용자1'
};
currentUser = mockUser;
localStorage.setItem('authToken', 'mock-token-' + Date.now());
localStorage.setItem('currentUser', JSON.stringify(mockUser));
showMainApp();
} else {
throw new Error('잘못된 사용자명 또는 비밀번호입니다.');
}
// 실제 API 호출 (백엔드 구현 후 사용)
/*
// 실제 API 호출
const response = await AuthAPI.login(username, password);
currentUser = response.user;
showMainApp();
*/
window.currentUser = response.user;
// 대시보드로 리다이렉트
window.location.href = 'dashboard.html';
} catch (error) {
console.error('로그인 실패:', error);
@@ -94,8 +110,25 @@ function logout() {
// 로그인 화면 표시
function showLoginScreen() {
document.getElementById('loginScreen').classList.remove('hidden');
document.getElementById('mainApp').classList.add('hidden');
const loginScreen = document.getElementById('loginScreen');
const mainApp = document.getElementById('mainApp');
const alreadyLoggedIn = document.getElementById('alreadyLoggedIn');
if (loginScreen) {
loginScreen.classList.remove('hidden');
} else {
// dashboard.html에서는 로그인 페이지로 리다이렉트
window.location.href = 'index.html';
return;
}
if (mainApp) {
mainApp.classList.add('hidden');
}
if (alreadyLoggedIn) {
alreadyLoggedIn.classList.add('hidden');
}
// 폼 초기화
const loginForm = document.getElementById('loginForm');
@@ -104,15 +137,54 @@ function showLoginScreen() {
}
}
// 이미 로그인됨 표시
function showAlreadyLoggedIn() {
const loginScreen = document.getElementById('loginScreen');
const alreadyLoggedIn = document.getElementById('alreadyLoggedIn');
if (loginScreen) {
loginScreen.classList.remove('hidden');
}
if (alreadyLoggedIn) {
alreadyLoggedIn.classList.remove('hidden');
}
// 로그인 폼 숨기기
const loginForm = document.getElementById('loginForm');
const testLoginSection = loginForm?.parentElement?.querySelector('.mt-4');
if (loginForm) {
loginForm.style.display = 'none';
}
if (testLoginSection) {
testLoginSection.style.display = 'none';
}
}
// 메인 앱 표시
function showMainApp() {
document.getElementById('loginScreen').classList.add('hidden');
document.getElementById('mainApp').classList.remove('hidden');
const loginScreen = document.getElementById('loginScreen');
const mainApp = document.getElementById('mainApp');
// index.html에서는 대시보드로 리다이렉트
if (!mainApp && loginScreen) {
window.location.href = 'dashboard.html';
return;
}
if (loginScreen) {
loginScreen.classList.add('hidden');
}
if (mainApp) {
mainApp.classList.remove('hidden');
}
// 사용자 정보 표시
const currentUserElement = document.getElementById('currentUser');
if (currentUserElement && currentUser) {
currentUserElement.textContent = currentUser.full_name || currentUser.username;
if (currentUserElement && window.currentUser) {
currentUserElement.textContent = window.currentUser.full_name || window.currentUser.username;
}
// Todo 목록 로드

View File

@@ -66,7 +66,7 @@ async function handleTodoSubmit(event) {
const newTodo = {
id: Date.now(),
...todoData,
user_id: currentUser?.id || 1
user_id: window.currentUser?.id || 1
};
todos.unshift(newTodo);
@@ -181,32 +181,8 @@ function clearForm() {
// Todo 목록 로드
async function loadTodos() {
try {
// 임시 데이터 (백엔드 구현 전까지)
if (todos.length === 0) {
todos = [
{
id: 1,
content: '프로젝트 문서 검토',
status: 'active',
photo: null,
created_at: new Date(Date.now() - 86400000).toISOString(),
user_id: 1
},
{
id: 2,
content: '회의 준비',
status: 'completed',
photo: null,
created_at: new Date(Date.now() - 172800000).toISOString(),
user_id: 1
}
];
}
// 실제 API 호출 (백엔드 구현 후 사용)
/*
// 실제 API 호출
todos = await TodoAPI.getTodos(currentFilter);
*/
renderTodos();
@@ -245,7 +221,7 @@ function renderTodos() {
<div class="todo-item p-4 hover:bg-gray-50 transition-colors">
<div class="flex items-start space-x-4">
<!-- 체크박스 -->
<button onclick="toggleTodo(${todo.id})" class="mt-1 flex-shrink-0">
<button onclick="toggleTodo('${todo.id}')" class="mt-1 flex-shrink-0">
<i class="fas ${todo.status === 'completed' ? 'fa-check-circle text-green-500' : 'fa-circle text-gray-300'} text-lg"></i>
</button>
@@ -270,11 +246,11 @@ function renderTodos() {
<!-- 액션 버튼 -->
<div class="flex-shrink-0 flex space-x-2">
${todo.status !== 'completed' ? `
<button onclick="editTodo(${todo.id})" class="text-gray-400 hover:text-blue-500">
<button onclick="editTodo('${todo.id}')" class="text-gray-400 hover:text-blue-500">
<i class="fas fa-edit"></i>
</button>
` : ''}
<button onclick="deleteTodo(${todo.id})" class="text-gray-400 hover:text-red-500">
<button onclick="deleteTodo('${todo.id}')" class="text-gray-400 hover:text-red-500">
<i class="fas fa-trash"></i>
</button>
</div>
@@ -378,7 +354,10 @@ function getStatusText(status) {
// 날짜 포맷팅
function formatDate(dateString) {
if (!dateString) return '날짜 없음';
const date = new Date(dateString);
if (isNaN(date.getTime())) return '날짜 없음';
const now = new Date();
const diffTime = now - date;
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
@@ -426,46 +405,69 @@ function goToClassify() {
}
// 항목 등록 후 인덱스 업데이트
function updateItemCounts() {
// TODO: API에서 각 분류별 항목 수를 가져와서 업데이트
// 임시로 하드코딩된 값 사용
const todoCount = document.getElementById('todoCount');
const calendarCount = document.getElementById('calendarCount');
const checklistCount = document.getElementById('checklistCount');
if (todoCount) todoCount.textContent = '2개';
if (calendarCount) calendarCount.textContent = '3개';
if (checklistCount) checklistCount.textContent = '5개';
async function updateItemCounts() {
try {
// 무한 로딩 방지: 토큰이 없으면 API 요청하지 않음
const token = localStorage.getItem('authToken');
if (!token) {
console.log('토큰이 없어서 카운트 업데이트를 건너뜁니다.');
// 토큰이 없으면 0개로 표시
const todoCountEl = document.getElementById('todoCount');
const calendarCountEl = document.getElementById('calendarCount');
const checklistCountEl = document.getElementById('checklistCount');
if (todoCountEl) todoCountEl.textContent = '0개';
if (calendarCountEl) calendarCountEl.textContent = '0개';
if (checklistCountEl) checklistCountEl.textContent = '0개';
return;
}
// API에서 실제 데이터 가져와서 카운트
const items = await TodoAPI.getTodos();
const todoCount = items.filter(item => item.category === 'todo').length;
const calendarCount = items.filter(item => item.category === 'calendar').length;
const checklistCount = items.filter(item => item.category === 'checklist').length;
const todoCountEl = document.getElementById('todoCount');
const calendarCountEl = document.getElementById('calendarCount');
const checklistCountEl = document.getElementById('checklistCount');
if (todoCountEl) todoCountEl.textContent = `${todoCount}`;
if (calendarCountEl) calendarCountEl.textContent = `${calendarCount}`;
if (checklistCountEl) checklistCountEl.textContent = `${checklistCount}`;
} catch (error) {
console.error('항목 카운트 업데이트 실패:', error);
// 에러 시 0개로 표시
const todoCountEl = document.getElementById('todoCount');
const calendarCountEl = document.getElementById('calendarCount');
const checklistCountEl = document.getElementById('checklistCount');
if (todoCountEl) todoCountEl.textContent = '0개';
if (calendarCountEl) calendarCountEl.textContent = '0개';
if (checklistCountEl) checklistCountEl.textContent = '0개';
}
}
// 등록된 항목들 로드
function loadRegisteredItems() {
// 임시 데이터 (실제로는 API에서 가져옴)
const sampleItems = [
{
id: 1,
content: '프로젝트 문서 정리',
photo_url: null,
category: null,
created_at: '2024-01-15'
},
{
id: 2,
content: '회의 자료 준비',
photo_url: null,
category: 'todo',
created_at: '2024-01-16'
},
{
id: 3,
content: '월말 보고서 작성',
photo_url: null,
category: 'calendar',
created_at: '2024-01-17'
// 등록된 항목들 로드 (카테고리가 없는 미분류 항목들)
async function loadRegisteredItems() {
try {
// 무한 로딩 방지: 토큰이 없으면 API 요청하지 않음
const token = localStorage.getItem('authToken');
if (!token) {
console.log('토큰이 없어서 API 요청을 건너뜁니다.');
renderRegisteredItems([]);
return;
}
];
renderRegisteredItems(sampleItems);
// API에서 모든 항목을 가져와서 카테고리가 없는 것만 필터링
const allItems = await TodoAPI.getTodos();
const unclassifiedItems = allItems.filter(item => !item.category || item.category === null);
renderRegisteredItems(unclassifiedItems);
} catch (error) {
console.error('등록된 항목 로드 실패:', error);
renderRegisteredItems([]);
}
}
// 등록된 항목들 렌더링
@@ -484,18 +486,18 @@ function renderRegisteredItems(items) {
emptyState.classList.add('hidden');
itemsList.innerHTML = items.map(item => `
<div class="p-6 hover:bg-gray-50 cursor-pointer transition-colors" onclick="showClassificationModal(${item.id})">
<div class="p-6 hover:bg-gray-50 cursor-pointer transition-colors" onclick="showClassificationModal('${item.id}')">
<div class="flex items-start space-x-4">
<!-- 사진 (있는 경우) -->
${item.photo_url ? `
${item.image_url ? `
<div class="flex-shrink-0">
<img src="${item.photo_url}" class="w-16 h-16 object-cover rounded-lg" alt="첨부 사진">
<img src="${item.image_url}" class="w-16 h-16 object-cover rounded-lg" alt="첨부 사진">
</div>
` : ''}
<!-- 내용 -->
<div class="flex-1 min-w-0">
<h4 class="text-gray-900 font-medium mb-2">${item.content}</h4>
<h4 class="text-gray-900 font-medium mb-2">${item.title}</h4>
<div class="flex items-center space-x-4 text-sm text-gray-500">
<span>
<i class="fas fa-clock mr-1"></i>등록: ${formatDate(item.created_at)}
@@ -523,32 +525,229 @@ function renderRegisteredItems(items) {
// 분류 모달 표시
function showClassificationModal(itemId) {
// TODO: 분류 선택 모달 구현
console.log('분류 모달 표시:', itemId);
// 임시로 confirm으로 분류 선택
const choice = prompt('분류를 선택하세요:\n1. Todo (시작 날짜)\n2. 캘린더 (마감 기한)\n3. 체크리스트 (기한 없음)\n\n번호를 입력하세요:');
// 기존 항목 정보 가져오기
const item = todos.find(t => t.id == itemId) || { title: '', description: '' };
if (choice) {
const categories = {
'1': 'todo',
'2': 'calendar',
'3': 'checklist'
// 모달 HTML 생성
const modalHtml = `
<div id="classificationModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-full max-w-md mx-4">
<h3 class="text-lg font-semibold mb-4">항목 분류 및 편집</h3>
<!-- 제목 입력 -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">제목</label>
<input type="text" id="itemTitle" value="${item.title || ''}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
placeholder="할 일을 입력하세요">
</div>
<!-- 설명 입력 -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">설명 (선택사항)</label>
<textarea id="itemDescription" rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
placeholder="상세 설명을 입력하세요">${item.description || ''}</textarea>
</div>
<!-- 카테고리 선택 -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">분류 선택</label>
<div class="grid grid-cols-3 gap-2">
<button type="button" onclick="selectCategory('todo')"
class="category-btn p-3 border-2 border-gray-200 rounded-lg text-center hover:border-blue-500 transition-colors"
data-category="todo">
<i class="fas fa-calendar-day text-blue-500 text-xl mb-1"></i>
<div class="text-sm font-medium">Todo</div>
<div class="text-xs text-gray-500">시작 날짜</div>
</button>
<button type="button" onclick="selectCategory('calendar')"
class="category-btn p-3 border-2 border-gray-200 rounded-lg text-center hover:border-orange-500 transition-colors"
data-category="calendar">
<i class="fas fa-calendar-times text-orange-500 text-xl mb-1"></i>
<div class="text-sm font-medium">캘린더</div>
<div class="text-xs text-gray-500">마감 기한</div>
</button>
<button type="button" onclick="selectCategory('checklist')"
class="category-btn p-3 border-2 border-gray-200 rounded-lg text-center hover:border-green-500 transition-colors"
data-category="checklist">
<i class="fas fa-check-square text-green-500 text-xl mb-1"></i>
<div class="text-sm font-medium">체크리스트</div>
<div class="text-xs text-gray-500">기한 없음</div>
</button>
</div>
</div>
<!-- 날짜 선택 (카테고리에 따라 표시) -->
<div id="dateSection" class="mb-4 hidden">
<label class="block text-sm font-medium text-gray-700 mb-2" id="dateLabel">날짜 선택</label>
<input type="date" id="itemDate"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
</div>
<!-- 우선순위 선택 -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">우선순위</label>
<select id="itemPriority" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
<option value="low">낮음</option>
<option value="medium" selected>보통</option>
<option value="high">높음</option>
</select>
</div>
<!-- 버튼 -->
<div class="flex space-x-3">
<button onclick="closeClassificationModal()"
class="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50">
취소
</button>
<button onclick="saveClassification('${itemId}')"
class="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
확인
</button>
</div>
</div>
</div>
`;
// 모달을 body에 추가
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 현재 선택된 카테고리 변수 (기본값: todo)
window.selectedCategory = 'todo';
// 기본적으로 todo 카테고리 선택 상태로 표시
setTimeout(() => {
selectCategory('todo');
}, 100);
// 모달 외부 클릭 시 닫기
const modal = document.getElementById('classificationModal');
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeClassificationModal();
}
});
// ESC 키로 모달 닫기
const handleEscKey = (e) => {
if (e.key === 'Escape') {
closeClassificationModal();
document.removeEventListener('keydown', handleEscKey);
}
};
document.addEventListener('keydown', handleEscKey);
}
// 카테고리 선택
function selectCategory(category) {
// 이전 선택 해제
document.querySelectorAll('.category-btn').forEach(btn => {
btn.classList.remove('border-blue-500', 'border-orange-500', 'border-green-500', 'bg-blue-50', 'bg-orange-50', 'bg-green-50');
btn.classList.add('border-gray-200');
});
// 새 선택 적용
const selectedBtn = document.querySelector(`[data-category="${category}"]`);
if (selectedBtn) {
selectedBtn.classList.remove('border-gray-200');
if (category === 'todo') {
selectedBtn.classList.add('border-blue-500', 'bg-blue-50');
} else if (category === 'calendar') {
selectedBtn.classList.add('border-orange-500', 'bg-orange-50');
} else if (category === 'checklist') {
selectedBtn.classList.add('border-green-500', 'bg-green-50');
}
}
// 날짜 섹션 표시/숨김
const dateSection = document.getElementById('dateSection');
const dateLabel = document.getElementById('dateLabel');
if (category === 'checklist') {
dateSection.classList.add('hidden');
} else {
dateSection.classList.remove('hidden');
if (category === 'todo') {
dateLabel.textContent = '시작 날짜';
} else if (category === 'calendar') {
dateLabel.textContent = '마감 날짜';
}
}
window.selectedCategory = category;
}
// 분류 모달 닫기
function closeClassificationModal() {
const modal = document.getElementById('classificationModal');
if (modal) {
modal.remove();
}
window.selectedCategory = null;
}
// 분류 저장
async function saveClassification(itemId) {
const title = document.getElementById('itemTitle').value.trim();
const description = document.getElementById('itemDescription').value.trim();
const priority = document.getElementById('itemPriority').value;
const date = document.getElementById('itemDate').value;
if (!title) {
alert('제목을 입력해주세요.');
return;
}
if (!window.selectedCategory) {
alert('분류를 선택해주세요.');
return;
}
try {
// API 호출하여 항목 업데이트
const updateData = {
title: title,
description: description,
category: window.selectedCategory,
priority: priority
};
const category = categories[choice];
if (category) {
classifyItem(itemId, category);
// 날짜 설정 (체크리스트가 아닌 경우)
if (window.selectedCategory !== 'checklist' && date) {
updateData.due_date = date + 'T09:00:00Z'; // 기본 시간 설정
}
await TodoAPI.updateTodo(itemId, updateData);
// 성공 메시지
showToast(`항목이 ${getCategoryText(window.selectedCategory)}(으)로 분류되었습니다.`, 'success');
// 모달 닫기
closeClassificationModal();
// todo가 아닌 다른 카테고리로 변경한 경우에만 페이지 이동
if (window.selectedCategory !== 'todo') {
setTimeout(() => {
goToPage(window.selectedCategory);
}, 1000); // 토스트 메시지를 보여준 후 이동
} else {
// todo 카테고리인 경우 인덱스 페이지에서 목록만 새로고침
loadRegisteredItems();
updateItemCounts();
}
} catch (error) {
console.error('분류 저장 실패:', error);
alert('분류 저장에 실패했습니다.');
}
}
// 항목 분류
// 항목 분류 (기존 함수 - 호환성 유지)
function classifyItem(itemId, category) {
// TODO: API 호출하여 항목 분류 업데이트
console.log('항목 분류:', itemId, category);
// 분류 후 해당 페이지로 이동
goToPage(category);
}

View File

@@ -162,28 +162,15 @@
}
// Todo 항목 로드
function loadTodoItems() {
// 임시 데이터 (실제로는 API에서 가져옴)
const todoItems = [
{
id: 1,
content: '프로젝트 기획서 작성',
photo: null,
start_date: '2024-01-20',
status: 'scheduled',
created_at: '2024-01-15'
},
{
id: 2,
content: '팀 미팅 준비',
photo: null,
start_date: '2024-01-18',
status: 'active',
created_at: '2024-01-16'
}
];
renderTodoItems(todoItems);
async function loadTodoItems() {
try {
// API에서 Todo 카테고리 항목들만 가져오기
const items = await TodoAPI.getTodos(null, 'todo');
renderTodoItems(items);
} catch (error) {
console.error('Todo 항목 로드 실패:', error);
renderTodoItems([]);
}
}
// Todo 항목 렌더링
@@ -218,10 +205,10 @@
<!-- 내용 -->
<div class="flex-1 min-w-0">
<h4 class="text-gray-900 font-medium mb-2">${item.content}</h4>
<h4 class="text-gray-900 font-medium mb-2">${item.title}</h4>
<div class="flex items-center space-x-4 text-sm text-gray-500">
<span>
<i class="fas fa-calendar mr-1"></i>시작일: ${formatDate(item.start_date)}
<i class="fas fa-calendar mr-1"></i>마감일: ${formatDate(item.due_date)}
</span>
<span>
<i class="fas fa-clock mr-1"></i>등록: ${formatDate(item.created_at)}
@@ -232,14 +219,17 @@
<!-- 액션 버튼 -->
<div class="flex-shrink-0 flex space-x-2">
${item.status !== 'completed' ? `
<button onclick="startTodo(${item.id})" class="text-blue-500 hover:text-blue-700" title="시작하기">
<i class="fas fa-play"></i>
<button onclick="delayTodo('${item.id}')" class="text-orange-500 hover:text-orange-700" title="지연하기">
<i class="fas fa-clock"></i>
</button>
<button onclick="completeTodo(${item.id})" class="text-green-500 hover:text-green-700" title="완료하기">
<button onclick="completeTodo('${item.id}')" class="text-green-500 hover:text-green-700" title="완료하기">
<i class="fas fa-check"></i>
</button>
<button onclick="splitTodo('${item.id}')" class="text-purple-500 hover:text-purple-700" title="분할하기">
<i class="fas fa-cut"></i>
</button>
` : ''}
<button onclick="editTodo(${item.id})" class="text-gray-400 hover:text-blue-500" title="수정하기">
<button onclick="editTodo('${item.id}')" class="text-gray-400 hover:text-blue-500" title="수정하기">
<i class="fas fa-edit"></i>
</button>
</div>
@@ -270,26 +260,68 @@
// 날짜 포맷팅
function formatDate(dateString) {
if (!dateString) return '날짜 없음';
const date = new Date(dateString);
if (isNaN(date.getTime())) return '날짜 없음';
return date.toLocaleDateString('ko-KR');
}
// Todo 시작
function startTodo(id) {
console.log('Todo 시작:', id);
// TODO: API 호출하여 상태를 'active'로 변경
// Todo 지연
function delayTodo(id) {
const newDate = prompt('새로운 시작 날짜를 입력하세요 (YYYY-MM-DD):');
if (newDate && /^\d{4}-\d{2}-\d{2}$/.test(newDate)) {
// TODO: API 호출하여 due_date 업데이트
console.log('Todo 지연:', id, '새 날짜:', newDate);
alert(`할 일이 ${newDate}로 지연되었습니다.`);
loadTodoItems(); // 목록 새로고침
} else if (newDate) {
alert('올바른 날짜 형식을 입력해주세요 (YYYY-MM-DD)');
}
}
// Todo 완료
function completeTodo(id) {
console.log('Todo 완료:', id);
// TODO: API 호출하여 상태를 'completed'로 변경
async function completeTodo(id) {
try {
// TODO: API 호출하여 상태를 'completed'로 변경
console.log('Todo 완료:', id);
alert('할 일이 완료되었습니다!');
loadTodoItems(); // 목록 새로고침
} catch (error) {
console.error('완료 처리 실패:', error);
alert('완료 처리에 실패했습니다.');
}
}
// Todo 분할
function splitTodo(id) {
const splitCount = prompt('몇 개로 분할하시겠습니까? (2-5개):');
const count = parseInt(splitCount);
if (count >= 2 && count <= 5) {
const subtasks = [];
for (let i = 1; i <= count; i++) {
const subtask = prompt(`${i}번째 세부 작업을 입력하세요:`);
if (subtask) {
subtasks.push(subtask.trim());
}
}
if (subtasks.length > 0) {
// TODO: API 호출하여 원본 삭제 후 세부 작업들 생성
console.log('Todo 분할:', id, '세부 작업들:', subtasks);
alert(`할 일이 ${subtasks.length}개의 세부 작업으로 분할되었습니다.`);
loadTodoItems(); // 목록 새로고침
}
} else if (splitCount) {
alert('2-5개 사이의 숫자를 입력해주세요.');
}
}
// Todo 편집
function editTodo(id) {
console.log('Todo 편집:', id);
// TODO: 편집 모달 또는 페이지로 이동
alert('편집 기능은 준비 중입니다.');
}
// 필터링
@@ -306,5 +338,7 @@
// 전역 함수 등록
window.goToDashboard = goToDashboard;
</script>
<script src="static/js/api.js?v=20250921110800"></script>
<script src="static/js/auth.js?v=20250921110800"></script>
</body>
</html>