🎯 주요 개선사항: - 노트북 목록 조회/표시 기능 완성 - 노트북 생성/편집/삭제 모달 구현 - 토스트 알림 시스템 추가 (alert 대신) - 노트북 통계 대시보드 표시 - 노트북별 노트 관리 및 빠른 노트 생성 기능 - URL 파라미터를 통한 노트북 자동 설정 🔧 기술적 개선: - CSS line-clamp 클래스 추가 - 필드명 불일치 수정 (notebook.name → notebook.title) - 삭제 확인 모달로 UX 개선 - 노트 에디터에서 노트북 자동 설정 지원 📱 UI/UX 개선: - 노트북 카드 호버 효과 및 색상 테마 - 빈 노트북 상태 처리 및 안내 - 반응형 그리드 레이아웃 - 로딩 상태 및 에러 처리 개선
311 lines
8.8 KiB
JavaScript
311 lines
8.8 KiB
JavaScript
// 노트북 관리 애플리케이션 컴포넌트
|
|
window.notebooksApp = () => ({
|
|
// 상태 관리
|
|
notebooks: [],
|
|
stats: null,
|
|
loading: false,
|
|
saving: false,
|
|
error: '',
|
|
|
|
// 알림 시스템
|
|
notification: {
|
|
show: false,
|
|
message: '',
|
|
type: 'info' // 'success', 'error', 'info'
|
|
},
|
|
|
|
// 필터링
|
|
searchQuery: '',
|
|
activeOnly: true,
|
|
sortBy: 'updated_at',
|
|
|
|
// 검색 디바운스
|
|
searchTimeout: null,
|
|
|
|
// 모달 상태
|
|
showCreateModal: false,
|
|
showEditModal: false,
|
|
showDeleteModal: false,
|
|
editingNotebook: null,
|
|
deletingNotebook: null,
|
|
deleting: false,
|
|
|
|
// 노트북 폼
|
|
notebookForm: {
|
|
title: '',
|
|
description: '',
|
|
color: '#3B82F6',
|
|
icon: 'book',
|
|
is_active: true,
|
|
sort_order: 0
|
|
},
|
|
|
|
// 인증 상태
|
|
isAuthenticated: false,
|
|
currentUser: null,
|
|
|
|
// API 클라이언트
|
|
api: null,
|
|
|
|
// 색상 옵션
|
|
availableColors: [
|
|
'#3B82F6', // blue
|
|
'#10B981', // emerald
|
|
'#F59E0B', // amber
|
|
'#EF4444', // red
|
|
'#8B5CF6', // violet
|
|
'#06B6D4', // cyan
|
|
'#84CC16', // lime
|
|
'#F97316', // orange
|
|
'#EC4899', // pink
|
|
'#6B7280' // gray
|
|
],
|
|
|
|
// 아이콘 옵션
|
|
availableIcons: [
|
|
{ value: 'book', label: '📖 책' },
|
|
{ value: 'sticky-note', label: '📝 노트' },
|
|
{ value: 'lightbulb', label: '💡 아이디어' },
|
|
{ value: 'graduation-cap', label: '🎓 학습' },
|
|
{ value: 'briefcase', label: '💼 업무' },
|
|
{ value: 'heart', label: '❤️ 개인' },
|
|
{ value: 'code', label: '💻 개발' },
|
|
{ value: 'palette', label: '🎨 창작' },
|
|
{ value: 'flask', label: '🧪 연구' },
|
|
{ value: 'star', label: '⭐ 즐겨찾기' }
|
|
],
|
|
|
|
// 초기화
|
|
async init() {
|
|
console.log('📚 Notebooks App 초기화 시작');
|
|
|
|
// API 클라이언트 초기화
|
|
this.api = new DocumentServerAPI();
|
|
|
|
// 인증 상태 확인
|
|
await this.checkAuthStatus();
|
|
|
|
if (this.isAuthenticated) {
|
|
await this.loadStats();
|
|
await this.loadNotebooks();
|
|
}
|
|
|
|
// 헤더 로드
|
|
await this.loadHeader();
|
|
},
|
|
|
|
// 인증 상태 확인
|
|
async checkAuthStatus() {
|
|
try {
|
|
const user = await this.api.getCurrentUser();
|
|
this.isAuthenticated = true;
|
|
this.currentUser = user;
|
|
console.log('✅ 인증됨:', user.username || user.email);
|
|
} catch (error) {
|
|
console.log('❌ 인증되지 않음');
|
|
this.isAuthenticated = false;
|
|
this.currentUser = null;
|
|
window.location.href = '/';
|
|
}
|
|
},
|
|
|
|
// 헤더 로드
|
|
async loadHeader() {
|
|
try {
|
|
if (typeof loadHeaderComponent === 'function') {
|
|
await loadHeaderComponent();
|
|
} else if (typeof window.loadHeaderComponent === 'function') {
|
|
await window.loadHeaderComponent();
|
|
} else {
|
|
console.warn('헤더 로더 함수를 찾을 수 없습니다.');
|
|
}
|
|
} catch (error) {
|
|
console.error('헤더 로드 실패:', error);
|
|
}
|
|
},
|
|
|
|
// 통계 정보 로드
|
|
async loadStats() {
|
|
try {
|
|
this.stats = await this.api.getNotebookStats();
|
|
console.log('📊 노트북 통계 로드됨:', this.stats);
|
|
} catch (error) {
|
|
console.error('통계 로드 실패:', error);
|
|
}
|
|
},
|
|
|
|
// 노트북 목록 로드
|
|
async loadNotebooks() {
|
|
this.loading = true;
|
|
this.error = '';
|
|
|
|
try {
|
|
const queryParams = {
|
|
active_only: this.activeOnly,
|
|
sort_by: this.sortBy,
|
|
order: 'desc'
|
|
};
|
|
|
|
if (this.searchQuery) {
|
|
queryParams.search = this.searchQuery;
|
|
}
|
|
|
|
this.notebooks = await this.api.getNotebooks(queryParams);
|
|
console.log('📚 노트북 로드됨:', this.notebooks.length, '개');
|
|
} catch (error) {
|
|
console.error('노트북 로드 실패:', error);
|
|
this.error = '노트북을 불러오는 중 오류가 발생했습니다.';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
// 검색 디바운스
|
|
debounceSearch() {
|
|
clearTimeout(this.searchTimeout);
|
|
this.searchTimeout = setTimeout(() => {
|
|
this.loadNotebooks();
|
|
}, 300);
|
|
},
|
|
|
|
// 새로고침
|
|
async refreshNotebooks() {
|
|
await Promise.all([
|
|
this.loadStats(),
|
|
this.loadNotebooks()
|
|
]);
|
|
},
|
|
|
|
// 노트북 열기 (노트 목록으로 이동)
|
|
openNotebook(notebook) {
|
|
window.location.href = `/notes.html?notebook_id=${notebook.id}¬ebook_name=${encodeURIComponent(notebook.title)}`;
|
|
},
|
|
|
|
// 노트북에 노트 생성
|
|
createNoteInNotebook(notebook) {
|
|
window.location.href = `/note-editor.html?notebook_id=${notebook.id}¬ebook_name=${encodeURIComponent(notebook.title)}`;
|
|
},
|
|
|
|
// 노트북 편집
|
|
editNotebook(notebook) {
|
|
this.editingNotebook = notebook;
|
|
this.notebookForm = {
|
|
title: notebook.title,
|
|
description: notebook.description || '',
|
|
color: notebook.color,
|
|
icon: notebook.icon,
|
|
is_active: notebook.is_active,
|
|
sort_order: notebook.sort_order
|
|
};
|
|
this.showEditModal = true;
|
|
},
|
|
|
|
// 노트북 삭제 (모달 표시)
|
|
deleteNotebook(notebook) {
|
|
this.deletingNotebook = notebook;
|
|
this.showDeleteModal = true;
|
|
},
|
|
|
|
// 삭제 확인
|
|
async confirmDeleteNotebook() {
|
|
if (!this.deletingNotebook) return;
|
|
|
|
this.deleting = true;
|
|
try {
|
|
await this.api.deleteNotebook(this.deletingNotebook.id, true); // force=true
|
|
this.showNotification('노트북이 삭제되었습니다.', 'success');
|
|
this.closeDeleteModal();
|
|
await this.refreshNotebooks();
|
|
} catch (error) {
|
|
console.error('노트북 삭제 실패:', error);
|
|
this.showNotification('노트북 삭제에 실패했습니다.', 'error');
|
|
} finally {
|
|
this.deleting = false;
|
|
}
|
|
},
|
|
|
|
// 삭제 모달 닫기
|
|
closeDeleteModal() {
|
|
this.showDeleteModal = false;
|
|
this.deletingNotebook = null;
|
|
this.deleting = false;
|
|
},
|
|
|
|
// 노트북 저장
|
|
async saveNotebook() {
|
|
if (!this.notebookForm.title.trim()) {
|
|
this.showNotification('제목을 입력해주세요.', 'error');
|
|
return;
|
|
}
|
|
|
|
this.saving = true;
|
|
|
|
try {
|
|
if (this.showEditModal && this.editingNotebook) {
|
|
// 편집
|
|
await this.api.updateNotebook(this.editingNotebook.id, this.notebookForm);
|
|
this.showNotification('노트북이 수정되었습니다.', 'success');
|
|
} else {
|
|
// 생성
|
|
await this.api.createNotebook(this.notebookForm);
|
|
this.showNotification('노트북이 생성되었습니다.', 'success');
|
|
}
|
|
|
|
this.closeModal();
|
|
await this.refreshNotebooks();
|
|
} catch (error) {
|
|
console.error('노트북 저장 실패:', error);
|
|
this.showNotification('노트북 저장에 실패했습니다.', 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
// 모달 닫기
|
|
closeModal() {
|
|
this.showCreateModal = false;
|
|
this.showEditModal = false;
|
|
this.editingNotebook = null;
|
|
this.notebookForm = {
|
|
title: '',
|
|
description: '',
|
|
color: '#3B82F6',
|
|
icon: 'book',
|
|
is_active: true,
|
|
sort_order: 0
|
|
};
|
|
},
|
|
|
|
// 날짜 포맷팅
|
|
formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
const now = new Date();
|
|
const diffTime = Math.abs(now - date);
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays === 1) {
|
|
return '오늘';
|
|
} else if (diffDays === 2) {
|
|
return '어제';
|
|
} else if (diffDays <= 7) {
|
|
return `${diffDays - 1}일 전`;
|
|
} else {
|
|
return date.toLocaleDateString('ko-KR');
|
|
}
|
|
},
|
|
|
|
// 알림 표시
|
|
showNotification(message, type = 'info') {
|
|
this.notification = {
|
|
show: true,
|
|
message: message,
|
|
type: type
|
|
};
|
|
|
|
// 3초 후 자동으로 숨김
|
|
setTimeout(() => {
|
|
this.notification.show = false;
|
|
}, 3000);
|
|
}
|
|
});
|