Spaces:
Running
Running
| <html lang="tr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Bartın Fen Lisesi Kütüphane Takip Sistemi</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"> | |
| <!-- Firebase SDK --> | |
| <script src="https://www.gstatic.com/firebasejs/9.6.0/firebase-app-compat.js"></script> | |
| <script src="https://www.gstatic.com/firebasejs/9.6.0/firebase-firestore-compat.js"></script> | |
| <script src="https://www.gstatic.com/firebasejs/9.6.0/firebase-auth-compat.js"></script> | |
| <style> | |
| body { | |
| background-color: #f5f0e6; | |
| background-image: url('https://cdn.discordapp.com/attachments/1205496867694190604/1371135928302702702/k_31095013_okul_rsm_2.jpg?ex=68220957&is=6820b7d7&hm=7b8f37259e98230e49e4a46f993a8126d9aef68d43fd71457f9e1b302773532e&'); | |
| background-size: cover; | |
| background-position: center; | |
| background-attachment: fixed; | |
| background-blend-mode: overlay; | |
| background-color: rgba(245, 240, 230, 0.7); | |
| } | |
| .content-bg { | |
| background-color: rgba(245, 240, 230, 0.9); | |
| } | |
| .card-bg { | |
| background-color: #ede3d3; | |
| } | |
| .button-primary { | |
| background-color: #8b6b61; | |
| color: white; | |
| } | |
| .button-primary:hover { | |
| background-color: #6d4c41; | |
| } | |
| .button-secondary { | |
| background-color: #d6c7b0; | |
| color: #3e2723; | |
| } | |
| .button-secondary:hover { | |
| background-color: #bca892; | |
| } | |
| .nav-item.active { | |
| border-bottom: 3px solid #8b6b61; | |
| color: #5d4037; | |
| } | |
| .header-bg { | |
| background-color: #5d4037; | |
| color: white; | |
| } | |
| .footer-bg { | |
| background-color: #3e2723; | |
| color: white; | |
| } | |
| [x-cloak] { display: none ; } | |
| .security-warning { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| background-color: #d32f2f; | |
| color: white; | |
| text-align: center; | |
| padding: 10px; | |
| font-weight: bold; | |
| z-index: 1000; | |
| display: none; | |
| } | |
| .loading-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 2000; | |
| } | |
| .spinner { | |
| border: 4px solid rgba(0, 0, 0, 0.1); | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 50%; | |
| border-left-color: #8b6b61; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body class="font-sans" x-data="libraryApp()" x-cloak> | |
| <!-- Loading Overlay --> | |
| <div class="loading-overlay" x-show="isLoading"> | |
| <div class="spinner"></div> | |
| </div> | |
| <!-- Security Warning --> | |
| <div class="security-warning" id="securityWarning"> | |
| <i class="fas fa-exclamation-triangle mr-2"></i> | |
| UYARI: Bu sistem sadece yetkili personel içindir. Yetkisiz erişim yasaktır! | |
| </div> | |
| <!-- Header --> | |
| <header class="header-bg"> | |
| <div class="container mx-auto px-4 py-4"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="flex items-center mb-4 md:mb-0"> | |
| <a href="#" @click.prevent="activeTab = 'catalog'" class="flex items-center"> | |
| <img src="https://cdn.discordapp.com/attachments/1205496867694190604/1371135714212839424/OKULOGOSU.png?ex=68220924&is=6820b7a4&hm=b1f6c1ba3afd33c1cf6d7b19bdb8c3a1e2d9a5f84e77ecb99d4dd5e71f277f68&" | |
| alt="Bartın Fen Lisesi Logo" | |
| class="h-16 mr-4"> | |
| <div> | |
| <h1 class="text-2xl md:text-3xl font-bold">Bartın Fen Lisesi</h1> | |
| <p class="opacity-90">Kütüphane Takip Sistemi</p> | |
| </div> | |
| </a> | |
| </div> | |
| <div class="flex flex-wrap justify-center gap-2"> | |
| <button @click="activeTab = 'catalog'" class="nav-item px-4 py-2 font-medium" :class="{'active': activeTab === 'catalog'}"> | |
| <i class="fas fa-book mr-2"></i>Kitap Kataloğu | |
| </button> | |
| <button @click="activeTab = 'borrow'" class="nav-item px-4 py-2 font-medium" :class="{'active': activeTab === 'borrow'}"> | |
| <i class="fas fa-hand-holding mr-2"></i>Ödünç Al | |
| </button> | |
| <button @click="activeTab = 'return'" class="nav-item px-4 py-2 font-medium" :class="{'active': activeTab === 'return'}"> | |
| <i class="fas fa-undo mr-2"></i>İade Et | |
| </button> | |
| <button @click="activeTab = 'history'" class="nav-item px-4 py-2 font-medium" :class="{'active': activeTab === 'history'}"> | |
| <i class="fas fa-history mr-2"></i>Geçmiş | |
| </button> | |
| <button @click="activeTab = 'admin'" class="nav-item px-4 py-2 font-medium bg-red-100 text-red-800 rounded" :class="{'active': activeTab === 'admin'}"> | |
| <i class="fas fa-lock mr-2"></i>Yönetici | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <!-- Catalog Tab --> | |
| <div x-show="activeTab === 'catalog'" class="space-y-8"> | |
| <div class="content-bg rounded-xl shadow-lg p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center"> | |
| <i class="fas fa-book mr-3 text-brown-600"></i> | |
| Kitap Kataloğu | |
| </h2> | |
| <div class="mb-6 flex flex-col md:flex-row justify-between items-start md:items-center space-y-4 md:space-y-0"> | |
| <div class="relative w-full md:w-64"> | |
| <input type="text" x-model="searchQuery" placeholder="Kitap adı, yazar veya tür ara..." | |
| class="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <select x-model="sortBy" class="border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <option value="title">Kitap Adı (A-Z)</option> | |
| <option value="titleDesc">Kitap Adı (Z-A)</option> | |
| <option value="author">Yazar (A-Z)</option> | |
| <option value="year">Yayın Yılı (Yeni-Eski)</option> | |
| <option value="yearDesc">Yayın Yılı (Eski-Yeni)</option> | |
| </select> | |
| <select x-model="filterStatus" class="border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <option value="all">Tüm Kitaplar</option> | |
| <option value="available">Mevcut Kitaplar</option> | |
| <option value="borrowed">Ödünçteki Kitaplar</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <template x-for="book in filteredBooks" :key="book.id"> | |
| <div class="card-bg border rounded-lg overflow-hidden shadow-md transition-all hover:transform hover:-translate-y-1 hover:shadow-lg"> | |
| <div class="h-48 bg-gray-200 flex items-center justify-center"> | |
| <template x-if="book.cover"> | |
| <img :src="book.cover" :alt="book.title" class="h-full w-full object-cover"> | |
| </template> | |
| <template x-if="!book.cover"> | |
| <i class="fas fa-book-open text-5xl text-gray-500"></i> | |
| </template> | |
| </div> | |
| <div class="p-4"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h3 class="text-lg font-bold text-gray-800" x-text="book.title"></h3> | |
| <span class="px-2 py-1 text-xs rounded-full" | |
| :class="book.status === 'available' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'" | |
| x-text="book.status === 'available' ? 'Mevcut' : 'Ödünçte'"></span> | |
| </div> | |
| <p class="text-gray-600 mb-1" x-text="'Yazar: ' + book.author"></p> | |
| <p class="text-gray-600 mb-1" x-text="'Tür: ' + book.genre"></p> | |
| <p class="text-gray-600 mb-3" x-text="'Yayın Yılı: ' + book.year"></p> | |
| <template x-if="book.status === 'available'"> | |
| <button @click="activeTab = 'borrow'; selectedBookId = book.id" | |
| class="w-full button-primary py-2 rounded-lg transition"> | |
| Ödünç Al | |
| </button> | |
| </template> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| </div> | |
| <!-- Popular Books Section --> | |
| <div class="content-bg rounded-xl shadow-lg p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center"> | |
| <i class="fas fa-star mr-3 text-yellow-500"></i> | |
| En Çok Okunan Kitaplar | |
| </h2> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kitap Adı</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Yazar</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ödünç Alma Sayısı</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Durum</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200"> | |
| <template x-for="book in popularBooks" :key="book.id"> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900" x-text="book.title"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="book.author"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="book.borrowCount"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm"> | |
| <span class="px-2 py-1 text-xs rounded-full" | |
| :class="book.status === 'available' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'" | |
| x-text="book.status === 'available' ? 'Mevcut' : 'Ödünçte'"></span> | |
| </td> | |
| </tr> | |
| </template> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Borrow Tab --> | |
| <div x-show="activeTab === 'borrow'" class="content-bg rounded-xl shadow-lg p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center"> | |
| <i class="fas fa-hand-holding mr-3 text-brown-600"></i> | |
| Kitap Ödünç Alma | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Kitap Bilgileri</h3> | |
| <template x-if="selectedBookId"> | |
| <div class="border rounded-lg p-4 mb-6 card-bg"> | |
| <template x-for="book in books" :key="book.id"> | |
| <template x-if="book.id === selectedBookId"> | |
| <div> | |
| <div class="flex items-start mb-4"> | |
| <div class="w-24 h-32 bg-gray-200 flex items-center justify-center mr-4"> | |
| <template x-if="book.cover"> | |
| <img :src="book.cover" :alt="book.title" class="h-full w-full object-cover"> | |
| </template> | |
| <template x-if="!book.cover"> | |
| <i class="fas fa-book-open text-3xl text-gray-500"></i> | |
| </template> | |
| </div> | |
| <div> | |
| <h4 class="font-bold text-gray-800" x-text="book.title"></h4> | |
| <p class="text-gray-600 text-sm" x-text="'Yazar: ' + book.author"></p> | |
| <p class="text-gray-600 text-sm" x-text="'Yayın Yılı: ' + book.year"></p> | |
| <p class="text-gray-600 text-sm" x-text="'Tür: ' + book.genre"></p> | |
| </div> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <span class="px-2 py-1 text-xs rounded-full" | |
| :class="book.status === 'available' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'" | |
| x-text="book.status === 'available' ? 'Mevcut' : 'Ödünçte'"></span> | |
| <button @click="selectedBookId = null" class="text-sm text-brown-600 hover:text-brown-800"> | |
| Başka Kitap Seç | |
| </button> | |
| </div> | |
| </div> | |
| </template> | |
| </template> | |
| </div> | |
| </template> | |
| <template x-if="!selectedBookId"> | |
| <div class="border-2 border-dashed rounded-lg p-8 text-center mb-6 card-bg"> | |
| <i class="fas fa-book-open text-4xl text-gray-400 mb-3"></i> | |
| <p class="text-gray-500">Ödünç almak için bir kitap seçin</p> | |
| <button @click="activeTab = 'catalog'" class="mt-4 text-brown-600 hover:text-brown-800 font-medium"> | |
| Katalogdan Seç <i class="fas fa-arrow-right ml-1"></i> | |
| </button> | |
| </div> | |
| </template> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Öğrenci Bilgileri</h3> | |
| <form @submit.prevent="borrowBook" class="space-y-4"> | |
| <div> | |
| <label for="studentName" class="block text-sm font-medium text-gray-700 mb-1">Ad Soyad</label> | |
| <input type="text" id="studentName" x-model="borrowForm.name" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="studentNumber" class="block text-sm font-medium text-gray-700 mb-1">Okul Numarası</label> | |
| <input type="text" id="studentNumber" x-model="borrowForm.number" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div class="pt-2"> | |
| <button type="submit" :disabled="!selectedBookId" | |
| class="w-full button-primary py-2 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed"> | |
| Ödünç Al | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Return Tab --> | |
| <div x-show="activeTab === 'return'" class="content-bg rounded-xl shadow-lg p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center"> | |
| <i class="fas fa-undo mr-3 text-brown-600"></i> | |
| Kitap İade Etme | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Öğrenci Bilgileri</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label for="returnStudentNumber" class="block text-sm font-medium text-gray-700 mb-1">Okul Numarası</label> | |
| <input type="text" id="returnStudentNumber" x-model="returnForm.number" @input="findBorrowedBooks" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">İade Edilecek Kitaplar</h3> | |
| <template x-if="borrowedBooks.length > 0"> | |
| <div class="space-y-4"> | |
| <template x-for="book in borrowedBooks" :key="book.id"> | |
| <div class="border rounded-lg p-4 flex justify-between items-center card-bg"> | |
| <div> | |
| <h4 class="font-bold text-gray-800" x-text="book.title"></h4> | |
| <p class="text-gray-600 text-sm" x-text="'Ödünç Tarihi: ' + formatDate(book.borrowDate)"></p> | |
| </div> | |
| <button @click="returnBook(book.id)" class="px-4 py-2 button-primary rounded-lg transition"> | |
| İade Et | |
| </button> | |
| </div> | |
| </template> | |
| </div> | |
| </template> | |
| <template x-if="returnForm.number && borrowedBooks.length === 0"> | |
| <div class="border-2 border-dashed rounded-lg p-8 text-center card-bg"> | |
| <i class="fas fa-book text-4xl text-gray-400 mb-3"></i> | |
| <p class="text-gray-500">Bu numaraya ait ödünç alınmış kitap bulunamadı</p> | |
| </div> | |
| </template> | |
| <template x-if="!returnForm.number"> | |
| <div class="border-2 border-dashed rounded-lg p-8 text-center card-bg"> | |
| <i class="fas fa-info-circle text-4xl text-gray-400 mb-3"></i> | |
| <p class="text-gray-500">İade etmek için okul numaranızı girin</p> | |
| </div> | |
| </template> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- History Tab --> | |
| <div x-show="activeTab === 'history'" class="content-bg rounded-xl shadow-lg p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center"> | |
| <i class="fas fa-history mr-3 text-brown-600"></i> | |
| Ödünç Alma Geçmişi | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Öğrenci Bilgileri</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label for="historyStudentNumber" class="block text-sm font-medium text-gray-700 mb-1">Okul Numarası</label> | |
| <input type="text" id="historyStudentNumber" x-model="historyForm.number" @input="findStudentHistory" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Geçmiş Kayıtlar</h3> | |
| <template x-if="studentHistory.length > 0"> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kitap Adı</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ödünç Tarihi</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">İade Tarihi</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200"> | |
| <template x-for="record in studentHistory" :key="record.id"> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900" x-text="record.title"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="formatDate(record.borrowDate)"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="record.returnDate ? formatDate(record.returnDate) : 'İade edilmedi'"></td> | |
| </tr> | |
| </template> | |
| </tbody> | |
| </table> | |
| </div> | |
| </template> | |
| <template x-if="historyForm.number && studentHistory.length === 0"> | |
| <div class="border-2 border-dashed rounded-lg p-8 text-center card-bg"> | |
| <i class="fas fa-book text-4xl text-gray-400 mb-3"></i> | |
| <p class="text-gray-500">Bu numaraya ait geçmiş kayıt bulunamadı</p> | |
| </div> | |
| </template> | |
| <template x-if="!historyForm.number"> | |
| <div class="border-2 border-dashed rounded-lg p-8 text-center card-bg"> | |
| <i class="fas fa-info-circle text-4xl text-gray-400 mb-3"></i> | |
| <p class="text-gray-500">Geçmişi görüntülemek için okul numaranızı girin</p> | |
| </div> | |
| </template> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Admin Tab --> | |
| <div x-show="activeTab === 'admin'" class="content-bg rounded-xl shadow-lg p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center"> | |
| <i class="fas fa-lock mr-3 text-red-600"></i> | |
| Yönetici Paneli | |
| </h2> | |
| <div class="mb-6"> | |
| <label for="adminPassword" class="block text-sm font-medium text-gray-700 mb-1">Yönetici Şifresi</label> | |
| <div class="flex space-x-2"> | |
| <input type="password" id="adminPassword" x-model="adminPassword" @keyup.enter="checkAdminPassword" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <button @click="checkAdminPassword" class="px-4 py-2 button-primary rounded-lg transition"> | |
| Giriş Yap | |
| </button> | |
| </div> | |
| <p x-show="adminError" class="mt-1 text-sm text-red-600" x-text="adminError"></p> | |
| </div> | |
| <template x-if="isAdmin"> | |
| <div class="space-y-8"> | |
| <!-- Current Borrowed Books --> | |
| <div> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
| <i class="fas fa-book-reader mr-2"></i> | |
| Ödünçte Olan Kitaplar | |
| </h3> | |
| <div class="mb-4 flex flex-col md:flex-row justify-between items-start md:items-center space-y-2 md:space-y-0"> | |
| <div class="relative w-full md:w-64"> | |
| <input type="text" x-model="adminBorrowedSearch" placeholder="Öğrenci veya kitap ara..." | |
| class="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
| </div> | |
| <button @click="exportToExcel('borrowed')" class="px-4 py-2 button-secondary rounded-lg transition"> | |
| <i class="fas fa-file-excel mr-2"></i>Excel'e Aktar | |
| </button> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminBorrowed('studentName')"> | |
| Öğrenci Adı <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminBorrowed('studentNumber')"> | |
| Okul No <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminBorrowed('title')"> | |
| Kitap Adı <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminBorrowed('borrowDate')"> | |
| Ödünç Tarihi <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | |
| İşlem | |
| </th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200"> | |
| <template x-for="record in filteredAdminBorrowed" :key="record.id"> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900" x-text="record.studentName"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="record.studentNumber"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="record.title"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="formatDate(record.borrowDate)"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
| <button @click="adminReturnBook(record.id)" class="text-red-600 hover:text-red-800"> | |
| <i class="fas fa-undo mr-1"></i>İade Et | |
| </button> | |
| </td> | |
| </tr> | |
| </template> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Returned Books History --> | |
| <div> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
| <i class="fas fa-clipboard-list mr-2"></i> | |
| İade Edilen Kitaplar Geçmişi | |
| </h3> | |
| <div class="mb-4 flex flex-col md:flex-row justify-between items-start md:items-center space-y-2 md:space-y-0"> | |
| <div class="relative w-full md:w-64"> | |
| <input type="text" x-model="adminReturnedSearch" placeholder="Öğrenci veya kitap ara..." | |
| class="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button @click="exportToExcel('returned')" class="px-4 py-2 button-secondary rounded-lg transition"> | |
| <i class="fas fa-file-excel mr-2"></i>Excel'e Aktar | |
| </button> | |
| <button @click="exportToPDF('returned')" class="px-4 py-2 button-primary rounded-lg transition"> | |
| <i class="fas fa-file-pdf mr-2"></i>PDF'ye Aktar | |
| </button> | |
| </div> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminReturned('studentName')"> | |
| Öğrenci Adı <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminReturned('studentNumber')"> | |
| Okul No <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminReturned('title')"> | |
| Kitap Adı <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminReturned('borrowDate')"> | |
| Ödünç Tarihi <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" @click="sortAdminReturned('returnDate')"> | |
| İade Tarihi <i class="fas fa-sort ml-1"></i> | |
| </th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200"> | |
| <template x-for="record in filteredAdminReturned" :key="record.id"> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900" x-text="record.studentName"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="record.studentNumber"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="record.title"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="formatDate(record.borrowDate)"></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500" x-text="formatDate(record.returnDate)"></td> | |
| </tr> | |
| </template> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Add/Edit Books --> | |
| <div> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
| <i class="fas fa-book-medical mr-2"></i> | |
| Kitap Yönetimi | |
| </h3> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| <div> | |
| <h4 class="text-lg font-medium text-gray-800 mb-4">Yeni Kitap Ekle</h4> | |
| <form @submit.prevent="addBook" class="space-y-4"> | |
| <div> | |
| <label for="newBookTitle" class="block text-sm font-medium text-gray-700 mb-1">Kitap Adı</label> | |
| <input type="text" id="newBookTitle" x-model="newBook.title" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="newBookAuthor" class="block text-sm font-medium text-gray-700 mb-1">Yazar</label> | |
| <input type="text" id="newBookAuthor" x-model="newBook.author" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="newBookGenre" class="block text-sm font-medium text-gray-700 mb-1">Tür</label> | |
| <input type="text" id="newBookGenre" x-model="newBook.genre" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="newBookYear" class="block text-sm font-medium text-gray-700 mb-1">Yayın Yılı</label> | |
| <input type="number" id="newBookYear" x-model="newBook.year" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="newBookISBN" class="block text-sm font-medium text-gray-700 mb-1">ISBN (İsteğe Bağlı)</label> | |
| <input type="text" id="newBookISBN" x-model="newBook.isbn" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="newBookCover" class="block text-sm font-medium text-gray-700 mb-1">Kapak Görseli URL (İsteğe Bağlı)</label> | |
| <input type="text" id="newBookCover" x-model="newBook.cover" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div class="pt-2"> | |
| <button type="submit" class="w-full button-primary py-2 rounded-lg transition"> | |
| Kitap Ekle | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| <div> | |
| <h4 class="text-lg font-medium text-gray-800 mb-4">Kitap Düzenle/Sil</h4> | |
| <div class="mb-4"> | |
| <label for="editBookSelect" class="block text-sm font-medium text-gray-700 mb-1">Kitap Seçin</label> | |
| <select id="editBookSelect" x-model="selectedEditBookId" @change="loadBookForEdit" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <option value="">Bir kitap seçin</option> | |
| <template x-for="book in books" :key="book.id"> | |
| <option :value="book.id" x-text="book.title"></option> | |
| </template> | |
| </select> | |
| </div> | |
| <template x-if="selectedEditBookId"> | |
| <form @submit.prevent="updateBook" class="space-y-4"> | |
| <div> | |
| <label for="editBookTitle" class="block text-sm font-medium text-gray-700 mb-1">Kitap Adı</label> | |
| <input type="text" id="editBookTitle" x-model="editBook.title" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="editBookAuthor" class="block text-sm font-medium text-gray-700 mb-1">Yazar</label> | |
| <input type="text" id="editBookAuthor" x-model="editBook.author" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="editBookGenre" class="block text-sm font-medium text-gray-700 mb-1">Tür</label> | |
| <input type="text" id="editBookGenre" x-model="editBook.genre" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="editBookYear" class="block text-sm font-medium text-gray-700 mb-1">Yayın Yılı</label> | |
| <input type="number" id="editBookYear" x-model="editBook.year" required | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="editBookISBN" class="block text-sm font-medium text-gray-700 mb-1">ISBN</label> | |
| <input type="text" id="editBookISBN" x-model="editBook.isbn" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="editBookCover" class="block text-sm font-medium text-gray-700 mb-1">Kapak Görseli URL</label> | |
| <input type="text" id="editBookCover" x-model="editBook.cover" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| </div> | |
| <div> | |
| <label for="editBookStatus" class="block text-sm font-medium text-gray-700 mb-1">Durum</label> | |
| <select id="editBookStatus" x-model="editBook.status" | |
| class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-brown-500"> | |
| <option value="available">Mevcut</option> | |
| <option value="borrowed">Ödünçte</option> | |
| </select> | |
| </div> | |
| <div class="flex space-x-2 pt-2"> | |
| <button type="submit" class="flex-1 button-primary py-2 rounded-lg transition"> | |
| Güncelle | |
| </button> | |
| <button type="button" @click="deleteBook" class="flex-1 bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg transition"> | |
| Sil | |
| </button> | |
| </div> | |
| </form> | |
| </template> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="footer-bg py-8"> | |
| <div class="container mx-auto px-4"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="mb-4 md:mb-0"> | |
| <h2 class="text-xl font-bold">Bartın Fen Lisesi</h2> | |
| <p class="text-gray-300">Kütüphane Takip Sistemi</p> | |
| </div> | |
| <div class="text-gray-300 text-sm"> | |
| <p>© 2023 Tüm hakları saklıdır.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </footer> | |
| <!-- Alpine.js and other scripts --> | |
| <script src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.0/dist/cdn.min.js" defer></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script> | |
| <script> | |
| // Firebase configuration | |
| const firebaseConfig = { | |
| apiKey: "AIzaSyD4XKx1Q2Q2X2X2X2X2X2X2X2X2X2X2X2X2", | |
| authDomain: "bartin-fen-lisesi-kutuphane.firebaseapp.com", | |
| projectId: "bartin-fen-lisesi-kutuphane", | |
| storageBucket: "bartin-fen-lisesi-kutuphane.appspot.com", | |
| messagingSenderId: "123456789012", | |
| appId: "1:123456789012:web:abcdefghijklmnopqrstuv" | |
| }; | |
| // Initialize Firebase | |
| firebase.initializeApp(firebaseConfig); | |
| const db = firebase.firestore(); | |
| const auth = firebase.auth(); | |
| // Library Application | |
| function libraryApp() { | |
| return { | |
| // Application State | |
| activeTab: 'catalog', | |
| isAdmin: false, | |
| adminPassword: '', | |
| adminError: '', | |
| failedAttempts: 0, | |
| lockoutTime: 0, | |
| isLoading: true, | |
| // Data | |
| books: [], | |
| transactions: [], | |
| // Forms | |
| borrowForm: { | |
| name: '', | |
| number: '' | |
| }, | |
| returnForm: { | |
| number: '' | |
| }, | |
| historyForm: { | |
| number: '' | |
| }, | |
| newBook: { | |
| title: '', | |
| author: '', | |
| genre: '', | |
| year: '', | |
| isbn: '', | |
| cover: '', | |
| status: 'available' | |
| }, | |
| editBook: { | |
| id: '', | |
| title: '', | |
| author: '', | |
| genre: '', | |
| year: '', | |
| isbn: '', | |
| cover: '', | |
| status: 'available' | |
| }, | |
| // UI State | |
| selectedBookId: null, | |
| selectedEditBookId: null, | |
| searchQuery: '', | |
| sortBy: 'title', | |
| filterStatus: 'all', | |
| adminBorrowedSearch: '', | |
| adminReturnedSearch: '', | |
| adminBorrowedSort: { field: 'borrowDate', direction: 'desc' }, | |
| adminReturnedSort: { field: 'returnDate', direction: 'desc' }, | |
| // Computed Properties | |
| get popularBooks() { | |
| return [...this.books] | |
| .sort((a, b) => b.borrowCount - a.borrowCount) | |
| .slice(0, 5); | |
| }, | |
| get filteredBooks() { | |
| let filtered = [...this.books]; | |
| // Apply search filter | |
| if (this.searchQuery) { | |
| const query = this.searchQuery.toLowerCase(); | |
| filtered = filtered.filter(book => | |
| book.title.toLowerCase().includes(query) || | |
| book.author.toLowerCase().includes(query) || | |
| book.genre.toLowerCase().includes(query) | |
| ); | |
| } | |
| // Apply status filter | |
| if (this.filterStatus !== 'all') { | |
| filtered = filtered.filter(book => book.status === this.filterStatus); | |
| } | |
| // Apply sorting | |
| switch (this.sortBy) { | |
| case 'title': | |
| filtered.sort((a, b) => a.title.localeCompare(b.title)); | |
| break; | |
| case 'titleDesc': | |
| filtered.sort((a, b) => b.title.localeCompare(a.title)); | |
| break; | |
| case 'author': | |
| filtered.sort((a, b) => a.author.localeCompare(b.author)); | |
| break; | |
| case 'year': | |
| filtered.sort((a, b) => b.year - a.year); | |
| break; | |
| case 'yearDesc': | |
| filtered.sort((a, b) => a.year - b.year); | |
| break; | |
| default: | |
| filtered.sort((a, b) => a.title.localeCompare(b.title)); | |
| } | |
| return filtered; | |
| }, | |
| get borrowedBooks() { | |
| if (!this.returnForm.number) return []; | |
| return this.transactions | |
| .filter(t => t.studentNumber === this.returnForm.number && !t.returnDate) | |
| .map(t => { | |
| const book = this.books.find(b => b.id === t.bookId); | |
| return { | |
| id: t.id, | |
| title: book.title, | |
| borrowDate: t.borrowDate | |
| }; | |
| }); | |
| }, | |
| get studentHistory() { | |
| if (!this.historyForm.number) return []; | |
| return this.transactions | |
| .filter(t => t.studentNumber === this.historyForm.number) | |
| .map(t => { | |
| const book = this.books.find(b => b.id === t.bookId); | |
| return { | |
| id: t.id, | |
| title: book.title, | |
| borrowDate: t.borrowDate, | |
| returnDate: t.returnDate | |
| }; | |
| }); | |
| }, | |
| get adminBorrowed() { | |
| return this.transactions | |
| .filter(t => !t.returnDate) | |
| .map(t => { | |
| const book = this.books.find(b => b.id === t.bookId); | |
| return { | |
| id: t.id, | |
| studentName: t.studentName, | |
| studentNumber: t.studentNumber, | |
| title: book.title, | |
| borrowDate: t.borrowDate, | |
| bookId: t.bookId | |
| }; | |
| }); | |
| }, | |
| get filteredAdminBorrowed() { | |
| let filtered = [...this.adminBorrowed]; | |
| // Apply search filter | |
| if (this.adminBorrowedSearch) { | |
| const query = this.adminBorrowedSearch.toLowerCase(); | |
| filtered = filtered.filter(record => | |
| record.studentName.toLowerCase().includes(query) || | |
| record.studentNumber.toLowerCase().includes(query) || | |
| record.title.toLowerCase().includes(query) | |
| ); | |
| } | |
| // Apply sorting | |
| const field = this.adminBorrowedSort.field; | |
| const direction = this.adminBorrowedSort.direction; | |
| filtered.sort((a, b) => { | |
| if (a[field] < b[field]) return direction === 'asc' ? -1 : 1; | |
| if (a[field] > b[field]) return direction === 'asc' ? 1 : -1; | |
| return 0; | |
| }); | |
| return filtered; | |
| }, | |
| get adminReturned() { | |
| return this.transactions | |
| .filter(t => t.returnDate) | |
| .map(t => { | |
| const book = this.books.find(b => b.id === t.bookId); | |
| return { | |
| id: t.id, | |
| studentName: t.studentName, | |
| studentNumber: t.studentNumber, | |
| title: book.title, | |
| borrowDate: t.borrowDate, | |
| returnDate: t.returnDate, | |
| bookId: t.bookId | |
| }; | |
| }); | |
| }, | |
| get filteredAdminReturned() { | |
| let filtered = [...this.adminReturned]; | |
| // Apply search filter | |
| if (this.adminReturnedSearch) { | |
| const query = this.adminReturnedSearch.toLowerCase(); | |
| filtered = filtered.filter(record => | |
| record.studentName.toLowerCase().includes(query) || | |
| record.studentNumber.toLowerCase().includes(query) || | |
| record.title.toLowerCase().includes(query) | |
| ); | |
| } | |
| // Apply sorting | |
| const field = this.adminReturnedSort.field; | |
| const direction = this.adminReturnedSort.direction; | |
| filtered.sort((a, b) => { | |
| if (a[field] < b[field]) return direction === 'asc' ? -1 : 1; | |
| if (a[field] > b[field]) return direction === 'asc' ? 1 : -1; | |
| return 0; | |
| }); | |
| return filtered; | |
| }, | |
| // Methods | |
| async init() { | |
| // Check if there's a lockout time in localStorage | |
| const storedLockout = localStorage.getItem('adminLockout'); | |
| if (storedLockout) { | |
| this.lockoutTime = parseInt(storedLockout); | |
| const remainingTime = this.lockoutTime - Date.now(); | |
| if (remainingTime > 0) { | |
| this.showSecurityWarning(); | |
| this.adminError = `Çok fazla başarısız giriş denemesi. Lütfen ${Math.ceil(remainingTime / 1000)} saniye sonra tekrar deneyin.`; | |
| } else { | |
| localStorage.removeItem('adminLockout'); | |
| this.lockoutTime = 0; | |
| this.failedAttempts = 0; | |
| } | |
| } | |
| // Check if admin is already logged in | |
| const adminSession = localStorage.getItem('adminSession'); | |
| if (adminSession && adminSession === 'active') { | |
| this.isAdmin = true; | |
| } | |
| // Load data from Firestore | |
| try { | |
| // Load books | |
| const booksSnapshot = await db.collection('books').get(); | |
| this.books = booksSnapshot.docs.map(doc => ({ | |
| id: doc.id, | |
| ...doc.data() | |
| })); | |
| // Load transactions | |
| const transactionsSnapshot = await db.collection('transactions').get(); | |
| this.transactions = transactionsSnapshot.docs.map(doc => ({ | |
| id: doc.id, | |
| ...doc.data() | |
| })); | |
| this.isLoading = false; | |
| // Set up real-time listeners | |
| db.collection('books').onSnapshot(snapshot => { | |
| this.books = snapshot.docs.map(doc => ({ | |
| id: doc.id, | |
| ...doc.data() | |
| })); | |
| }); | |
| db.collection('transactions').onSnapshot(snapshot => { | |
| this.transactions = snapshot.docs.map(doc => ({ | |
| id: doc.id, | |
| ...doc.data() | |
| })); | |
| }); | |
| } catch (error) { | |
| console.error("Veri yüklenirken hata oluştu:", error); | |
| this.isLoading = false; | |
| } | |
| }, | |
| showSecurityWarning() { | |
| const warning = document.getElementById('securityWarning'); | |
| warning.style.display = 'block'; | |
| setTimeout(() => { | |
| warning.style.display = 'none'; | |
| }, 5000); | |
| }, | |
| formatDate(dateString) { | |
| if (!dateString) return ''; | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('tr-TR'); | |
| }, | |
| async checkAdminPassword() { | |
| // Check if account is locked | |
| if (this.lockoutTime > Date.now()) { | |
| const remainingTime = Math.ceil((this.lockoutTime - Date.now()) / 1000); | |
| this.adminError = `Hesap geçici olarak kilitlendi. Lütfen ${remainingTime} saniye sonra tekrar deneyin.`; | |
| this.showSecurityWarning(); | |
| return; | |
| } | |
| try { | |
| // Sign in with email and password (in a real app, this would be proper authentication) | |
| // For demo purposes, we're using a simple password check | |
| if (this.adminPassword === 'tcttnctn') { | |
| this.isAdmin = true; | |
| this.adminError = ''; | |
| this.failedAttempts = 0; | |
| localStorage.setItem('adminSession', 'active'); | |
| } else { | |
| this.failedAttempts++; | |
| if (this.failedAttempts >= 3) { | |
| // Lock account for 5 minutes | |
| this.lockoutTime = Date.now() + (5 * 60 * 1000); | |
| localStorage.setItem('adminLockout', this.lockoutTime.toString()); | |
| this.adminError = `Çok fazla başarısız giriş denemesi. Lütfen 5 dakika sonra tekrar deneyin.`; | |
| this.showSecurityWarning(); | |
| } else { | |
| this.adminError = 'Geçersiz şifre! Lütfen tekrar deneyin.'; | |
| } | |
| } | |
| // Clear password field | |
| this.adminPassword = ''; | |
| } catch (error) { | |
| console.error("Giriş hatası:", error); | |
| this.adminError = 'Giriş sırasında bir hata oluştu.'; | |
| } | |
| }, | |
| async borrowBook() { | |
| if (!this.selectedBookId) return; | |
| try { | |
| this.isLoading = true; | |
| // Find the book | |
| const bookRef = db.collection('books').doc(this.selectedBookId); | |
| const bookDoc = await bookRef.get(); | |
| if (!bookDoc.exists) { | |
| alert('Kitap bulunamadı!'); | |
| this.isLoading = false; | |
| return; | |
| } | |
| const bookData = bookDoc.data(); | |
| // Check if book is available | |
| if (bookData.status !== 'available') { | |
| alert('Bu kitap şu anda ödünçte!'); | |
| this.isLoading = false; | |
| return; | |
| } | |
| // Create transaction in Firestore | |
| const newTransaction = { | |
| bookId: this.selectedBookId, | |
| studentName: this.borrowForm.name, | |
| studentNumber: this.borrowForm.number, | |
| borrowDate: new Date().toISOString(), | |
| returnDate: null | |
| }; | |
| await db.collection('transactions').add(newTransaction); | |
| // Update book status in Firestore | |
| await bookRef.update({ | |
| status: 'borrowed', | |
| borrowCount: (bookData.borrowCount || 0) + 1 | |
| }); | |
| // Reset form | |
| this.borrowForm.name = ''; | |
| this.borrowForm.number = ''; | |
| this.selectedBookId = null; | |
| this.isLoading = false; | |
| // Show success message | |
| alert('Kitap ödünç alma işlemi başarılı!'); | |
| this.activeTab = 'catalog'; | |
| } catch (error) { | |
| console.error("Ödünç alma hatası:", error); | |
| this.isLoading = false; | |
| alert('Ödünç alma işlemi sırasında bir hata oluştu.'); | |
| } | |
| }, | |
| findBorrowedBooks() { | |
| // This is handled by the computed property | |
| }, | |
| async returnBook(transactionId) { | |
| try { | |
| this.isLoading = true; | |
| // Find transaction | |
| const transactionRef = db.collection('transactions').doc(transactionId); | |
| const transactionDoc = await transactionRef.get(); | |
| if (!transactionDoc.exists) { | |
| alert('İşlem bulunamadı!'); | |
| this.isLoading = false; | |
| return; | |
| } | |
| const transactionData = transactionDoc.data(); | |
| // Update transaction in Firestore | |
| await transactionRef.update({ | |
| returnDate: new Date().toISOString() | |
| }); | |
| // Update book status in Firestore | |
| await db.collection('books').doc(transactionData.bookId).update({ | |
| status: 'available' | |
| }); | |
| // Reset form | |
| this.returnForm.number = ''; | |
| this.isLoading = false; | |
| // Show success message | |
| alert('Kitap iade işlemi başarılı!'); | |
| } catch (error) { | |
| console.error("İade hatası:", error); | |
| this.isLoading = false; | |
| alert('İade işlemi sırasında bir hata oluştu.'); | |
| } | |
| }, | |
| findStudentHistory() { | |
| // This is handled by the computed property | |
| }, | |
| async adminReturnBook(transactionId) { | |
| try { | |
| this.isLoading = true; | |
| // Find transaction | |
| const transactionRef = db.collection('transactions').doc(transactionId); | |
| const transactionDoc = await transactionRef.get(); | |
| if (!transactionDoc.exists) { | |
| alert('İşlem bulunamadı!'); | |
| this.isLoading = false; | |
| return; | |
| } | |
| const transactionData = transactionDoc.data(); | |
| // Update transaction in Firestore | |
| await transactionRef.update({ | |
| returnDate: new Date().toISOString() | |
| }); | |
| // Update book status in Firestore | |
| await db.collection('books').doc(transactionData.bookId).update({ | |
| status: 'available' | |
| }); | |
| this.isLoading = false; | |
| // Show success message | |
| alert('Kitap iade işlemi başarılı!'); | |
| } catch (error) { | |
| console.error("İade hatası:", error); | |
| this.isLoading = false; | |
| alert('İade işlemi sırasında bir hata oluştu.'); | |
| } | |
| }, | |
| sortAdminBorrowed(field) { | |
| if (this.adminBorrowedSort.field === field) { | |
| this.adminBorrowedSort.direction = this.adminBorrowedSort.direction === 'asc' ? 'desc' : 'asc'; | |
| } else { | |
| this.adminBorrowedSort.field = field; | |
| this.adminBorrowedSort.direction = 'asc'; | |
| } | |
| }, | |
| sortAdminReturned(field) { | |
| if (this.adminReturnedSort.field === field) { | |
| this.adminReturnedSort.direction = this.adminReturnedSort.direction === 'asc' ? 'desc' : 'asc'; | |
| } else { | |
| this.adminReturnedSort.field = field; | |
| this.adminReturnedSort.direction = 'asc'; | |
| } | |
| }, | |
| exportToExcel(type) { | |
| const data = type === 'borrowed' ? this.filteredAdminBorrowed : this.filteredAdminReturned; | |
| // Prepare worksheet | |
| const ws = XLSX.utils.json_to_sheet(data.map(item => { | |
| if (type === 'borrowed') { | |
| return { | |
| 'Öğrenci Adı': item.studentName, | |
| 'Okul No': item.studentNumber, | |
| 'Kitap Adı': item.title, | |
| 'Ödünç Tarihi': this.formatDate(item.borrowDate) | |
| }; | |
| } else { | |
| return { | |
| 'Öğrenci Adı': item.studentName, | |
| 'Okul No': item.studentNumber, | |
| 'Kitap Adı': item.title, | |
| 'Ödünç Tarihi': this.formatDate(item.borrowDate), | |
| 'İade Tarihi': this.formatDate(item.returnDate) | |
| }; | |
| } | |
| })); | |
| // Create workbook | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, type === 'borrowed' ? 'ÖdünçtekiKitaplar' : 'İadeEdilenKitaplar'); | |
| // Export | |
| XLSX.writeFile(wb, `${type === 'borrowed' ? 'Oduncte_Kitaplar' : 'Iade_Edilen_Kitaplar'}.xlsx`); | |
| }, | |
| exportToPDF(type) { | |
| const { jsPDF } = window.jspdf; | |
| const doc = new jsPDF(); | |
| // Add title | |
| doc.setFontSize(18); | |
| doc.text(`Bartın Fen Lisesi - ${type === 'borrowed' ? 'Ödünçteki Kitaplar' : 'İade Edilen Kitaplar'}`, 14, 15); | |
| // Add date | |
| doc.setFontSize(10); | |
| doc.text(`Oluşturulma Tarihi: ${new Date().toLocaleDateString('tr-TR')}`, 14, 22); | |
| // Prepare data | |
| const data = type === 'borrowed' ? this.filteredAdminBorrowed : this.filteredAdminReturned; | |
| const headers = type === 'borrowed' | |
| ? [['Öğrenci Adı', 'Okul No', 'Kitap Adı', 'Ödünç Tarihi']] | |
| : [['Öğrenci Adı', 'Okul No', 'Kitap Adı', 'Ödünç Tarihi', 'İade Tarihi']]; | |
| const rows = data.map(item => { | |
| if (type === 'borrowed') { | |
| return [ | |
| item.studentName, | |
| item.studentNumber, | |
| item.title, | |
| this.formatDate(item.borrowDate) | |
| ]; | |
| } else { | |
| return [ | |
| item.studentName, | |
| item.studentNumber, | |
| item.title, | |
| this.formatDate(item.borrowDate), | |
| this.formatDate(item.returnDate) | |
| ]; | |
| } | |
| }); | |
| // Add table | |
| doc.autoTable({ | |
| head: headers, | |
| body: rows, | |
| startY: 25, | |
| styles: { fontSize: 9 }, | |
| headStyles: { fillColor: [30, 58, 138] } | |
| }); | |
| // Save PDF | |
| doc.save(`${type === 'borrowed' ? 'Oduncte_Kitaplar' : 'Iade_Edilen_Kitaplar'}.pdf`); | |
| }, | |
| async addBook() { | |
| try { | |
| this.isLoading = true; | |
| // Add book to Firestore | |
| await db.collection('books').add({ | |
| title: this.newBook.title, | |
| author: this.newBook.author, | |
| genre: this.newBook.genre, | |
| year: this.newBook.year, | |
| isbn: this.newBook.isbn, | |
| cover: this.newBook.cover, | |
| status: 'available', | |
| borrowCount: 0 | |
| }); | |
| // Reset form | |
| this.newBook = { | |
| title: '', | |
| author: '', | |
| genre: '', | |
| year: '', | |
| isbn: '', | |
| cover: '', | |
| status: 'available' | |
| }; | |
| this.isLoading = false; | |
| // Show success message | |
| alert('Kitap başarıyla eklendi!'); | |
| } catch (error) { | |
| console.error("Kitap ekleme hatası:", error); | |
| this.isLoading = false; | |
| alert('Kitap ekleme işlemi sırasında bir hata oluştu.'); | |
| } | |
| }, | |
| async loadBookForEdit() { | |
| if (!this.selectedEditBookId) { | |
| this.editBook = { | |
| id: '', | |
| title: '', | |
| author: '', | |
| genre: '', | |
| year: '', | |
| isbn: '', | |
| cover: '', | |
| status: 'available' | |
| }; | |
| return; | |
| } | |
| try { | |
| const bookDoc = await db.collection('books').doc(this.selectedEditBookId).get(); | |
| if (bookDoc.exists) { | |
| const bookData = bookDoc.data(); | |
| this.editBook = { | |
| id: bookDoc.id, | |
| title: bookData.title, | |
| author: bookData.author, | |
| genre: bookData.genre, | |
| year: bookData.year, | |
| isbn: bookData.isbn, | |
| cover: bookData.cover, | |
| status: bookData.status | |
| }; | |
| } | |
| } catch (error) { | |
| console.error("Kitap yükleme hatası:", error); | |
| alert('Kitap bilgileri yüklenirken bir hata oluştu.'); | |
| } | |
| }, | |
| async updateBook() { | |
| try { | |
| this.isLoading = true; | |
| await db.collection('books').doc(this.editBook.id).update({ | |
| title: this.editBook.title, | |
| author: this.editBook.author, | |
| genre: this.editBook.genre, | |
| year: this.editBook.year, | |
| isbn: this.editBook.isbn, | |
| cover: this.editBook.cover, | |
| status: this.editBook.status | |
| }); | |
| // Reset form | |
| this.selectedEditBookId = null; | |
| this.editBook = { | |
| id: '', | |
| title: '', | |
| author: '', | |
| genre: '', | |
| year: '', | |
| isbn: '', | |
| cover: '', | |
| status: 'available' | |
| }; | |
| this.isLoading = false; | |
| // Show success message | |
| alert('Kitap başarıyla güncellendi!'); | |
| } catch (error) { | |
| console.error("Kitap güncelleme hatası:", error); | |
| this.isLoading = false; | |
| alert('Kitap güncelleme işlemi sırasında bir hata oluştu.'); | |
| } | |
| }, | |
| async deleteBook() { | |
| if (!confirm('Bu kitabı silmek istediğinize emin misiniz?')) return; | |
| try { | |
| this.isLoading = true; | |
| // Check if book is borrowed | |
| const bookDoc = await db.collection('books').doc(this.editBook.id).get(); | |
| const bookData = bookDoc.data(); | |
| if (bookData.status === 'borrowed') { | |
| alert('Ödünçte olan bir kitabı silemezsiniz!'); | |
| this.isLoading = false; | |
| return; | |
| } | |
| // Delete book from Firestore | |
| await db.collection('books').doc(this.editBook.id).delete(); | |
| // Reset form | |
| this.selectedEditBookId = null; | |
| this.editBook = { | |
| id: '', | |
| title: '', | |
| author: '', | |
| genre: '', | |
| year: '', | |
| isbn: '', | |
| cover: '', | |
| status: 'available' | |
| }; | |
| this.isLoading = false; | |
| // Show success message | |
| alert('Kitap başarıyla silindi!'); | |
| } catch (error) { | |
| console.error("Kitap silme hatası:", error); | |
| this.isLoading = false; | |
| alert('Kitap silme işlemi sırasında bir hata oluştu.'); | |
| } | |
| } | |
| }; | |
| } | |
| // Initialize the app when the page loads | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const app = Alpine.store('libraryApp'); | |
| if (app) { | |
| app.init(); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Alirelian/bflkutuphanesi" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |