주요 변경사항: - story-reader.html: pt-4 → pt-20 - pdf-manager.html: py-8 → pt-20 pb-8 - upload.html: py-8 → pt-20 pb-8 - index.html: py-8 → pt-20 pb-8 - search.html: py-8 → pt-20 pb-8 - memo-tree.html: pt-16 → pt-20 - notebooks.html: py-8 → pt-20 pb-8 - notes.html: py-8 → pt-20 pb-8 헤더(h-16, z-50)와의 충돌을 방지하기 위해 모든 페이지의 상단 여백을 pt-20(80px)으로 통일
317 lines
18 KiB
HTML
317 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>PDF 파일 관리 - Document Server</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
|
|
<style>
|
|
.line-clamp-2 {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen" x-data="pdfManagerApp()">
|
|
<!-- 헤더 -->
|
|
<div id="header-container"></div>
|
|
|
|
<!-- 메인 컨텐츠 -->
|
|
<main class="container mx-auto px-4 pt-20 pb-8">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900">PDF 파일 관리</h1>
|
|
<p class="text-gray-600 mt-2">업로드된 PDF 파일들을 관리하고 삭제할 수 있습니다</p>
|
|
</div>
|
|
|
|
<button @click="refreshPDFs()"
|
|
:disabled="loading"
|
|
class="flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white rounded-lg transition-colors">
|
|
<i class="fas fa-sync-alt mr-2" :class="{'fa-spin': loading}"></i>
|
|
<span>새로고침</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 통계 카드 -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center mr-4">
|
|
<i class="fas fa-file-pdf text-red-600 text-xl"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900">전체 PDF</h3>
|
|
<p class="text-2xl font-bold text-red-600" x-text="pdfDocuments.length"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mr-4">
|
|
<i class="fas fa-book text-blue-600 text-xl"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900">서적 포함</h3>
|
|
<p class="text-2xl font-bold text-blue-600" x-text="bookPDFs"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mr-4">
|
|
<i class="fas fa-link text-green-600 text-xl"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900">HTML 연결</h3>
|
|
<p class="text-2xl font-bold text-green-600" x-text="linkedPDFs"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
<div class="flex items-center">
|
|
<div class="w-12 h-12 bg-yellow-100 rounded-lg flex items-center justify-center mr-4">
|
|
<i class="fas fa-unlink text-yellow-600 text-xl"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900">독립 파일</h3>
|
|
<p class="text-2xl font-bold text-yellow-600" x-text="standalonePDFs"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PDF 목록 -->
|
|
<div class="bg-white rounded-lg shadow-sm border">
|
|
<div class="p-6 border-b border-gray-200">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-lg font-semibold text-gray-900">PDF 파일 목록</h2>
|
|
|
|
<!-- 필터 버튼 -->
|
|
<div class="flex flex-wrap gap-2">
|
|
<button @click="filterType = 'all'"
|
|
:class="filterType === 'all' ? 'bg-gray-600 text-white' : 'bg-gray-200 text-gray-700'"
|
|
class="px-3 py-1.5 rounded-lg text-sm transition-colors">
|
|
전체
|
|
</button>
|
|
<button @click="filterType = 'book'"
|
|
:class="filterType === 'book' ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700'"
|
|
class="px-3 py-1.5 rounded-lg text-sm transition-colors">
|
|
서적 포함
|
|
</button>
|
|
<button @click="filterType = 'linked'"
|
|
:class="filterType === 'linked' ? 'bg-green-600 text-white' : 'bg-gray-200 text-gray-700'"
|
|
class="px-3 py-1.5 rounded-lg text-sm transition-colors">
|
|
HTML 연결
|
|
</button>
|
|
<button @click="filterType = 'standalone'"
|
|
:class="filterType === 'standalone' ? 'bg-yellow-600 text-white' : 'bg-gray-200 text-gray-700'"
|
|
class="px-3 py-1.5 rounded-lg text-sm transition-colors">
|
|
독립 파일
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 로딩 상태 -->
|
|
<div x-show="loading" class="p-8 text-center">
|
|
<i class="fas fa-spinner fa-spin text-2xl text-gray-400 mb-2"></i>
|
|
<p class="text-gray-500">PDF 파일을 불러오는 중...</p>
|
|
</div>
|
|
|
|
<!-- PDF 목록 -->
|
|
<div x-show="!loading && filteredPDFs.length > 0" class="divide-y divide-gray-200">
|
|
<template x-for="pdf in filteredPDFs" :key="pdf.id">
|
|
<div class="p-6 hover:bg-gray-50 transition-colors">
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex items-start space-x-4 flex-1">
|
|
<!-- PDF 아이콘 -->
|
|
<div class="w-12 h-12 bg-red-100 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
<i class="fas fa-file-pdf text-red-600 text-xl"></i>
|
|
</div>
|
|
|
|
<!-- PDF 정보 -->
|
|
<div class="flex-1 min-w-0">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-1" x-text="pdf.title"></h3>
|
|
<p class="text-sm text-gray-500 mb-2" x-text="pdf.original_filename"></p>
|
|
<p class="text-sm text-gray-600 line-clamp-2" x-text="pdf.description || '설명이 없습니다'"></p>
|
|
|
|
<!-- 서적 정보 및 연결 상태 -->
|
|
<div class="mt-3 space-y-2">
|
|
<!-- 서적 정보 -->
|
|
<div x-show="pdf.book_title" class="flex items-center space-x-2">
|
|
<span class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm rounded-full">
|
|
<i class="fas fa-book mr-1"></i>
|
|
<span x-text="pdf.book_title"></span>
|
|
</span>
|
|
<span x-show="pdf.isLinked" class="inline-flex items-center px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">
|
|
<i class="fas fa-link mr-1"></i>
|
|
HTML 연결됨
|
|
</span>
|
|
</div>
|
|
|
|
<!-- 서적 없는 경우 -->
|
|
<div x-show="!pdf.book_title" class="flex items-center space-x-2">
|
|
<span class="inline-flex items-center px-3 py-1 bg-gray-100 text-gray-600 text-sm rounded-full">
|
|
<i class="fas fa-file mr-1"></i>
|
|
서적 미분류
|
|
</span>
|
|
<span x-show="pdf.isLinked" class="inline-flex items-center px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">
|
|
<i class="fas fa-link mr-1"></i>
|
|
HTML 연결됨
|
|
</span>
|
|
<span x-show="!pdf.isLinked" class="inline-flex items-center px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-full">
|
|
<i class="fas fa-unlink mr-1"></i>
|
|
독립 파일
|
|
</span>
|
|
</div>
|
|
|
|
<!-- 업로드 날짜 -->
|
|
<div class="flex items-center space-x-4">
|
|
<span class="text-sm text-gray-500">
|
|
<i class="fas fa-calendar mr-1"></i>
|
|
<span x-text="formatDate(pdf.created_at)"></span>
|
|
</span>
|
|
<span x-show="pdf.uploaded_by" class="text-sm text-gray-500">
|
|
<i class="fas fa-user mr-1"></i>
|
|
<span x-text="pdf.uploaded_by"></span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 액션 버튼 -->
|
|
<div class="flex items-center space-x-2 ml-4">
|
|
<button @click="previewPDF(pdf)"
|
|
class="p-2 text-gray-400 hover:text-green-600 transition-colors"
|
|
title="PDF 미리보기">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
|
|
<button @click="downloadPDF(pdf)"
|
|
class="p-2 text-gray-400 hover:text-blue-600 transition-colors"
|
|
title="PDF 다운로드">
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
|
|
<button x-show="currentUser && currentUser.is_admin"
|
|
@click="deletePDF(pdf)"
|
|
class="p-2 text-gray-400 hover:text-red-600 transition-colors"
|
|
title="PDF 삭제">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- 빈 상태 -->
|
|
<div x-show="!loading && filteredPDFs.length === 0" class="p-8 text-center">
|
|
<i class="fas fa-file-pdf text-gray-400 text-4xl mb-4"></i>
|
|
<h3 class="text-lg font-medium text-gray-900 mb-2">PDF 파일이 없습니다</h3>
|
|
<p class="text-gray-500">
|
|
<span x-show="filterType === 'all'">업로드된 PDF 파일이 없습니다</span>
|
|
<span x-show="filterType === 'book'">서적에 포함된 PDF 파일이 없습니다</span>
|
|
<span x-show="filterType === 'linked'">HTML과 연결된 PDF 파일이 없습니다</span>
|
|
<span x-show="filterType === 'standalone'">독립 PDF 파일이 없습니다</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- PDF 미리보기 모달 -->
|
|
<div x-show="showPreviewModal"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
|
|
@click.self="closePreview()">
|
|
<div class="bg-white rounded-2xl shadow-2xl max-w-6xl w-full max-h-[90vh] overflow-hidden">
|
|
<!-- 헤더 -->
|
|
<div class="flex items-center justify-between p-6 border-b border-gray-200">
|
|
<div class="flex items-center space-x-3">
|
|
<i class="fas fa-file-pdf text-red-600 text-xl"></i>
|
|
<div>
|
|
<h3 class="text-xl font-bold text-gray-900" x-text="previewPdf?.title"></h3>
|
|
<p class="text-sm text-gray-500" x-text="previewPdf?.original_filename"></p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<button @click="downloadPDF(previewPdf)"
|
|
class="px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-2">
|
|
<i class="fas fa-download"></i>
|
|
<span>다운로드</span>
|
|
</button>
|
|
<button @click="closePreview()"
|
|
class="text-gray-400 hover:text-gray-600 transition-colors">
|
|
<i class="fas fa-times text-xl"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PDF 뷰어 -->
|
|
<div class="p-6 overflow-y-auto" style="max-height: calc(90vh - 120px);">
|
|
<!-- PDF 미리보기 -->
|
|
<div x-show="previewPdf" class="mb-4">
|
|
<!-- PDF 뷰어 컨테이너 -->
|
|
<div class="border rounded-lg overflow-hidden bg-gray-100 relative" style="height: 600px;">
|
|
<!-- PDF iframe 뷰어 -->
|
|
<iframe x-show="!pdfPreviewError && !pdfPreviewLoading && pdfPreviewSrc"
|
|
class="w-full h-full border-0"
|
|
:src="pdfPreviewSrc"
|
|
@load="pdfPreviewLoaded = true"
|
|
@error="handlePdfPreviewError()">
|
|
</iframe>
|
|
|
|
<!-- PDF 로딩 상태 -->
|
|
<div x-show="pdfPreviewLoading" class="flex items-center justify-center h-full">
|
|
<div class="text-center">
|
|
<i class="fas fa-spinner fa-spin text-3xl text-gray-500 mb-4"></i>
|
|
<p class="text-gray-600 text-lg">PDF를 로드하는 중...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PDF 에러 상태 -->
|
|
<div x-show="pdfPreviewError" class="flex items-center justify-center h-full text-gray-500">
|
|
<div class="text-center">
|
|
<i class="fas fa-exclamation-triangle text-3xl mb-4 text-red-500"></i>
|
|
<p class="text-lg mb-4">PDF를 로드할 수 없습니다</p>
|
|
<button @click="retryPdfPreview()"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 mr-2">
|
|
다시 시도
|
|
</button>
|
|
<button @click="downloadPDF(previewPdf)"
|
|
class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700">
|
|
파일 다운로드
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- JavaScript 파일들 -->
|
|
<script src="/static/js/api.js?v=2025012384"></script>
|
|
<script src="/static/js/auth.js?v=2025012351"></script>
|
|
<script src="/static/js/header-loader.js?v=2025012351"></script>
|
|
<script src="/static/js/pdf-manager.js?v=2025012627"></script>
|
|
</body>
|
|
</html>
|