feat: 계층구조 뷰 및 완전한 하이라이트/메모 시스템 구현
주요 기능: - 📚 Book 및 BookCategory 모델 추가 (서적 그룹화) - 🏗️ 계층구조 뷰 (Book > Category > Document) 구현 - 🎨 완전한 하이라이트 시스템 (생성, 표시, 삭제) - 📝 통합 메모 관리 (추가, 수정, 삭제) - 🔄 그리드 뷰와 계층구조 뷰 간 완전 동기화 - 🛡️ 관리자 전용 문서 삭제 기능 - 🔧 모든 CORS 및 500 오류 해결 기술적 개선: - API 베이스 URL을 Nginx 프록시로 변경 (/api) - 외래키 제약 조건 해결 (삭제 순서 최적화) - SQLAlchemy 관계 로딩 최적화 (selectinload) - 프론트엔드 캐시 무효화 시스템 - Alpine.js 컴포넌트 구조 개선 UI/UX: - 계층구조 네비게이션 (사이드바 + 트리 구조) - 하이라이트 모드 토글 스위치 - 완전한 툴팁 기반 메모 관리 인터페이스 - 반응형 하이라이트 메뉴 (색상 선택) - 스마트 툴팁 위치 조정 (화면 경계 고려)
This commit is contained in:
@@ -12,9 +12,9 @@
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<!-- 메인 앱 -->
|
||||
<div x-data="documentApp" x-init="init()">
|
||||
<div x-data="documentApp()" x-init="init()">
|
||||
<!-- 로그인 모달 -->
|
||||
<div x-data="authModal" x-show="showLoginModal" x-cloak class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div x-data="authModal()" x-show="showLoginModal" x-cloak class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white rounded-lg p-8 max-w-md w-full mx-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900">로그인</h2>
|
||||
@@ -53,11 +53,15 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<!-- 로고 -->
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center space-x-4">
|
||||
<h1 class="text-xl font-bold text-gray-900">
|
||||
<i class="fas fa-file-alt mr-2"></i>
|
||||
Document Server
|
||||
</h1>
|
||||
<div class="flex space-x-2">
|
||||
<span class="px-3 py-1 text-sm bg-blue-100 text-blue-800 rounded-full">그리드 뷰</span>
|
||||
<a href="hierarchy.html" class="px-3 py-1 text-sm bg-gray-100 text-gray-600 rounded-full hover:bg-gray-200">계층구조 뷰</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 검색바 -->
|
||||
@@ -181,7 +185,16 @@
|
||||
<i class="fas fa-file-alt text-blue-500 text-xl"></i>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-600 text-sm mb-4 line-clamp-3" x-text="doc.description || '설명 없음'"></p>
|
||||
<p class="text-gray-600 text-sm mb-3 line-clamp-3" x-text="doc.description || '설명 없음'"></p>
|
||||
|
||||
<!-- 서적 정보 -->
|
||||
<div x-show="doc.book_title" class="mb-3 p-2 bg-green-50 border border-green-200 rounded-md">
|
||||
<div class="flex items-center text-sm">
|
||||
<i class="fas fa-book text-green-600 mr-2"></i>
|
||||
<span class="font-medium text-green-800" x-text="doc.book_title"></span>
|
||||
<span x-show="doc.book_author" class="text-green-600 ml-1" x-text="' by ' + doc.book_author"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-1 mb-4">
|
||||
<template x-for="tag in doc.tags" :key="tag">
|
||||
@@ -191,7 +204,24 @@
|
||||
|
||||
<div class="flex justify-between items-center text-sm text-gray-500">
|
||||
<span x-text="formatDate(doc.created_at)"></span>
|
||||
<span x-text="doc.uploader_name"></span>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span x-text="doc.uploader_name"></span>
|
||||
<!-- 액션 버튼들 -->
|
||||
<div class="flex space-x-1 ml-2">
|
||||
<button @click.stop="editDocument(doc)"
|
||||
class="p-1 text-gray-400 hover:text-blue-600 transition-colors"
|
||||
title="문서 수정">
|
||||
<i class="fas fa-edit text-sm"></i>
|
||||
</button>
|
||||
<!-- 관리자만 삭제 버튼 표시 -->
|
||||
<button x-show="currentUser && currentUser.is_admin"
|
||||
@click.stop="deleteDocument(doc.id)"
|
||||
class="p-1 text-gray-400 hover:text-red-600 transition-colors"
|
||||
title="문서 삭제 (관리자 전용)">
|
||||
<i class="fas fa-trash text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -225,7 +255,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div x-data="uploadModal">
|
||||
<div x-data="uploadModal()">
|
||||
<form @submit.prevent="upload">
|
||||
<!-- 파일 업로드 영역 -->
|
||||
<div class="mb-6">
|
||||
@@ -272,6 +302,98 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 서적 선택/생성 -->
|
||||
<div class="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-3">📚 서적 설정</label>
|
||||
|
||||
<!-- 서적 선택 방식 -->
|
||||
<div class="flex space-x-4 mb-4">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" x-model="bookSelectionMode" value="existing"
|
||||
class="mr-2 text-blue-600">
|
||||
<span class="text-sm">기존 서적에 추가</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" x-model="bookSelectionMode" value="new"
|
||||
class="mr-2 text-blue-600">
|
||||
<span class="text-sm">새 서적 생성</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" x-model="bookSelectionMode" value="none"
|
||||
class="mr-2 text-blue-600">
|
||||
<span class="text-sm">서적 없이 업로드</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 기존 서적 선택 -->
|
||||
<div x-show="bookSelectionMode === 'existing'" class="space-y-3">
|
||||
<div>
|
||||
<input type="text" x-model="bookSearchQuery" @input="searchBooks"
|
||||
placeholder="서적 제목으로 검색..."
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<!-- 검색 결과 -->
|
||||
<div x-show="searchedBooks.length > 0" class="max-h-40 overflow-y-auto border border-gray-200 rounded-md">
|
||||
<template x-for="book in searchedBooks" :key="book.id">
|
||||
<div @click="selectBook(book)"
|
||||
:class="selectedBook?.id === book.id ? 'bg-blue-100 border-blue-500' : 'hover:bg-gray-50'"
|
||||
class="p-3 border-b border-gray-200 cursor-pointer">
|
||||
<div class="font-medium text-sm" x-text="book.title"></div>
|
||||
<div class="text-xs text-gray-500">
|
||||
<span x-text="book.author || '저자 미상'"></span> ·
|
||||
<span x-text="book.document_count + '개 문서'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 선택된 서적 표시 -->
|
||||
<div x-show="selectedBook" class="p-3 bg-blue-50 border border-blue-200 rounded-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="font-medium text-blue-900" x-text="selectedBook?.title"></div>
|
||||
<div class="text-sm text-blue-700" x-text="selectedBook?.author || '저자 미상'"></div>
|
||||
</div>
|
||||
<button @click="selectedBook = null" class="text-blue-500 hover:text-blue-700">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 새 서적 생성 -->
|
||||
<div x-show="bookSelectionMode === 'new'" class="space-y-3">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<input type="text" x-model="newBook.title" @input="getSuggestions"
|
||||
placeholder="서적 제목 *" :required="bookSelectionMode === 'new'"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" x-model="newBook.author"
|
||||
placeholder="저자"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 유사 서적 추천 -->
|
||||
<div x-show="suggestions.length > 0" class="p-3 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<div class="text-sm font-medium text-yellow-800 mb-2">💡 유사한 서적이 있습니다:</div>
|
||||
<template x-for="suggestion in suggestions" :key="suggestion.id">
|
||||
<div @click="selectExistingFromSuggestion(suggestion)"
|
||||
class="p-2 bg-white border border-yellow-300 rounded cursor-pointer hover:bg-yellow-50 mb-1">
|
||||
<div class="text-sm font-medium" x-text="suggestion.title"></div>
|
||||
<div class="text-xs text-gray-600">
|
||||
<span x-text="suggestion.author || '저자 미상'"></span> ·
|
||||
<span x-text="Math.round(suggestion.similarity_score * 100) + '% 유사'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 문서 정보 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
@@ -356,5 +478,10 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- JavaScript 파일들 -->
|
||||
<script src="/static/js/api.js?v=2025012222"></script>
|
||||
<script src="/static/js/auth.js?v=2025012222"></script>
|
||||
<script src="/static/js/main.js?v=2025012222"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user