Files
hyungi cfb9485d4f 🚀 배포용: PDF 뷰어 개선 및 서적별 UI 데본씽크 스타일 적용
 주요 개선사항:
- PDF API 500 에러 수정 (한글 파일명 UTF-8 인코딩 처리)
- PDF 뷰어 기능 완전 구현 (PDF.js 통합, 네비게이션, 확대/축소)
- 서적별 문서 그룹화 UI 데본씽크 스타일로 개선
- PDF Manager 페이지 서적별 보기 기능 추가
- Alpine.js 로드 순서 최적화로 JavaScript 에러 해결

🎨 UI/UX 개선:
- 확장/축소 가능한 아코디언 스타일 서적 목록
- 간결하고 직관적인 데본씽크 스타일 인터페이스
- PDF 상태 표시 (HTML 연결, 서적 분류)
- 반응형 디자인 및 부드러운 애니메이션

🔧 기술적 개선:
- PDF.js 워커 설정 및 토큰 인증 처리
- 서적별 PDF 자동 그룹화 로직
- Alpine.js 컴포넌트 초기화 최적화
2025-09-05 07:13:49 +09:00

405 lines
13 KiB
JavaScript

// 노트 관리 애플리케이션 컴포넌트
window.notesApp = () => ({
// 상태 관리
notes: [],
stats: null,
loading: false,
error: '',
// 필터링
searchQuery: '',
selectedType: '',
publishedOnly: false,
selectedNotebook: '',
// 노트북 관련
availableNotebooks: [],
// 일괄 선택 관련
selectedNotes: [],
bulkNotebookId: '',
// 노트북 생성 관련
showCreateNotebookModal: false,
creatingNotebook: false,
newNotebookForm: {
name: '',
description: '',
color: '#3B82F6',
icon: 'book'
},
// 색상 및 아이콘 옵션
availableColors: [
'#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6',
'#06B6D4', '#84CC16', '#F97316', '#EC4899', '#6B7280'
],
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: '⭐ 즐겨찾기' }
],
// 검색 디바운스
searchTimeout: null,
// 인증 상태
isAuthenticated: false,
currentUser: null,
// API 클라이언트
api: null,
// 초기화
async init() {
console.log('🚀 Notes App 초기화 시작');
// API 클라이언트 초기화
this.api = new DocumentServerAPI();
console.log('🔧 API 클라이언트 초기화됨:', this.api);
console.log('🔧 getNotebooks 메서드 존재 여부:', typeof this.api.getNotebooks);
// URL 파라미터 확인 (노트북 필터)
const urlParams = new URLSearchParams(window.location.search);
const notebookId = urlParams.get('notebook_id');
const notebookName = urlParams.get('notebook_name');
if (notebookId) {
this.selectedNotebook = notebookId;
console.log('🔍 노트북 필터 적용:', notebookName || notebookId);
}
// 인증 상태 확인
await this.checkAuthStatus();
if (this.isAuthenticated) {
await this.loadNotebooks();
await this.loadStats();
await this.loadNotes();
}
// 헤더 로드
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 {
await window.headerLoader.loadHeader();
} catch (error) {
console.error('헤더 로드 실패:', error);
}
},
// 노트북 목록 로드
async loadNotebooks() {
try {
console.log('📚 노트북 로드 시작...');
console.log('🔧 API 메서드 확인:', typeof this.api.getNotebooks);
if (typeof this.api.getNotebooks !== 'function') {
throw new Error('getNotebooks 메서드가 존재하지 않습니다');
}
// 임시: 직접 API 호출
this.availableNotebooks = await this.api.get('/notebooks/', { active_only: true });
console.log('📚 노트북 로드됨:', this.availableNotebooks.length, '개');
} catch (error) {
console.error('노트북 로드 실패:', error);
this.availableNotebooks = [];
}
},
// 통계 정보 로드
async loadStats() {
try {
this.stats = await this.api.get('/note-documents/stats');
console.log('📊 통계 로드됨:', this.stats);
} catch (error) {
console.error('통계 로드 실패:', error);
}
},
// 노트 목록 로드
async loadNotes() {
this.loading = true;
this.error = '';
try {
const queryParams = {};
if (this.searchQuery) {
queryParams.search = this.searchQuery;
}
if (this.selectedType) {
queryParams.note_type = this.selectedType;
}
if (this.publishedOnly) {
queryParams.published_only = 'true';
}
if (this.selectedNotebook) {
if (this.selectedNotebook === 'unassigned') {
queryParams.notebook_id = 'null';
} else {
queryParams.notebook_id = this.selectedNotebook;
}
}
this.notes = await this.api.getNoteDocuments(queryParams);
console.log('📝 노트 로드됨:', this.notes.length, '개');
} catch (error) {
console.error('노트 로드 실패:', error);
this.error = '노트를 불러오는데 실패했습니다: ' + error.message;
this.notes = [];
} finally {
this.loading = false;
}
},
// 검색 디바운스
debounceSearch() {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.loadNotes();
}, 500);
},
// 노트 새로고침
async refreshNotes() {
await Promise.all([
this.loadStats(),
this.loadNotes()
]);
},
// 새 노트 생성
createNewNote() {
window.location.href = '/note-editor.html';
},
// 노트 보기 (뷰어 페이지로 이동)
viewNote(noteId) {
window.location.href = `/viewer.html?type=note&id=${noteId}`;
},
// 노트 편집
editNote(noteId) {
window.location.href = `/note-editor.html?id=${noteId}`;
},
// 노트 삭제
async deleteNote(note) {
if (!confirm(`"${note.title}" 노트를 삭제하시겠습니까?`)) {
return;
}
try {
const response = await fetch(`/api/note-documents/${note.id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
});
if (response.ok) {
this.showNotification('노트가 삭제되었습니다', 'success');
await this.refreshNotes();
} else {
throw new Error('삭제 실패');
}
} catch (error) {
console.error('노트 삭제 실패:', error);
this.showNotification('노트 삭제에 실패했습니다: ' + error.message, 'error');
}
},
// 노트 타입 라벨
getNoteTypeLabel(type) {
const labels = {
'note': '일반',
'research': '연구',
'summary': '요약',
'idea': '아이디어',
'guide': '가이드',
'reference': '참고'
};
return labels[type] || type;
},
// 날짜 포맷팅
formatDate(dateString) {
if (!dateString) return '';
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', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
},
// 알림 표시
showNotification(message, type = 'info') {
console.log(`${type.toUpperCase()}: ${message}`);
// 간단한 토스트 알림 생성
const toast = document.createElement('div');
toast.className = `fixed top-4 right-4 px-6 py-3 rounded-lg text-white z-50 ${
type === 'success' ? 'bg-green-600' :
type === 'error' ? 'bg-red-600' : 'bg-blue-600'
}`;
toast.textContent = message;
document.body.appendChild(toast);
// 3초 후 제거
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
},
// === 일괄 선택 관련 메서드 ===
// 노트 선택/해제
toggleNoteSelection(noteId) {
const index = this.selectedNotes.indexOf(noteId);
if (index > -1) {
this.selectedNotes.splice(index, 1);
} else {
this.selectedNotes.push(noteId);
}
},
// 선택 해제
clearSelection() {
this.selectedNotes = [];
this.bulkNotebookId = '';
},
// 선택된 노트들을 노트북에 할당
async assignToNotebook() {
if (!this.bulkNotebookId || this.selectedNotes.length === 0) {
this.showNotification('노트북을 선택하고 노트를 선택해주세요.', 'error');
return;
}
try {
// 각 노트를 업데이트
const updatePromises = this.selectedNotes.map(noteId =>
this.api.put(`/note-documents/${noteId}`, { notebook_id: this.bulkNotebookId })
);
await Promise.all(updatePromises);
this.showNotification(`${this.selectedNotes.length}개 노트가 노트북에 할당되었습니다.`, 'success');
// 선택 해제 및 새로고침
this.clearSelection();
await this.loadNotes();
} catch (error) {
console.error('노트북 할당 실패:', error);
this.showNotification('노트북 할당에 실패했습니다.', 'error');
}
},
// === 노트북 생성 관련 메서드 ===
// 노트북 생성 모달 닫기
closeCreateNotebookModal() {
this.showCreateNotebookModal = false;
this.newNotebookForm = {
name: '',
description: '',
color: '#3B82F6',
icon: 'book'
};
},
// 노트북 생성 및 노트 할당
async createNotebookAndAssign() {
if (!this.newNotebookForm.name.trim()) {
this.showNotification('노트북 이름을 입력해주세요.', 'error');
return;
}
this.creatingNotebook = true;
try {
// 1. 노트북 생성
const newNotebook = await this.api.post('/notebooks/', this.newNotebookForm);
console.log('📚 새 노트북 생성됨:', newNotebook.name);
// 2. 선택된 노트들이 있으면 할당
if (this.selectedNotes.length > 0) {
const updatePromises = this.selectedNotes.map(noteId =>
this.api.put(`/note-documents/${noteId}`, { notebook_id: newNotebook.id })
);
await Promise.all(updatePromises);
console.log(`📝 ${this.selectedNotes.length}개 노트가 새 노트북에 할당됨`);
}
this.showNotification(
`노트북 "${newNotebook.name}"이 생성되었습니다.${this.selectedNotes.length > 0 ? ` ${this.selectedNotes.length}개 노트가 할당되었습니다.` : ''}`,
'success'
);
// 3. 정리 및 새로고침
this.closeCreateNotebookModal();
this.clearSelection();
await this.loadNotebooks();
await this.loadNotes();
} catch (error) {
console.error('노트북 생성 실패:', error);
this.showNotification('노트북 생성에 실패했습니다.', 'error');
} finally {
this.creatingNotebook = false;
}
}
});
// 페이지 로드 시 초기화
document.addEventListener('DOMContentLoaded', () => {
console.log('📄 Notes 페이지 로드됨');
});