source: Books.php

main
Last change on this file was 75f74d9, checked in by Vlado 222039 <vlado.popovski@…>, 6 weeks ago

Initial commit: Adding Book Tracker code

  • Property mode set to 100644
File size: 17.0 KB
Line 
1<?php
2
3require 'connect.php';
4
5session_start();
6
7$distinct_genres = [];
8$sql_get_distinct_genres = "SELECT DISTINCT genre FROM Book";
9
10$stmt = $conn->prepare($sql_get_distinct_genres);
11$stmt->execute();
12
13while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
14 $distinct_genres[] = $row['genre'];
15}
16
17$year_query = "SELECT MIN(PublishedYear) as min_year, MAX(PublishedYear) as max_year FROM Book";
18$year_stmt = $conn->prepare($year_query);
19$year_stmt->execute();
20$year_range = $year_stmt->fetch(PDO::FETCH_ASSOC);
21
22
23$where_conditions = [];
24$params = [];
25
26$year_from = isset($_GET['year_from']) ? (int)$_GET['year_from'] : null;
27$year_to = isset($_GET['year_to']) ? (int)$_GET['year_to'] : null;
28
29$year_min = $year_range['min_year'];
30$year_max = $year_range['max_year'];
31
32$query_from = $year_from;
33$query_to = $year_to;
34
35if($year_from === null) {
36 $query_from = $year_min;
37}
38if($year_to === null) {
39 $query_to = $year_max;
40}
41
42 if(isset($_GET['genres'])) {
43 $genres_filters = $_GET['genres'];
44 $placeholders = str_repeat('?,', count($genres_filters) - 1) . '?';
45
46 $stmt = $conn->prepare("SELECT book.bookid, book.CoverImage, book.Title, book.Genre, book.PublishedYear, author.FirstName, author.LastName
47 FROM Book
48 INNER JOIN Book_Author ON Book.BookID = Book_Author.BookID
49 INNER JOIN Author ON Book_Author.AuthorID = Author.AuthorID
50 WHERE book.genre IN ($placeholders) AND book.PublishedYear BETWEEN ? AND ?;");
51
52 $stmt->execute(array_merge($genres_filters, [$query_from, $query_to]));
53 }
54 else {
55 $genres_filters = [];
56 $stmt = $conn->prepare("SELECT book.bookid, book.CoverImage, book.Title, book.Genre, book.PublishedYear, author.FirstName, author.LastName
57 FROM Book
58 INNER JOIN Book_Author ON Book.BookID = Book_Author.BookID
59 INNER JOIN Author ON Book_Author.AuthorID = Author.AuthorID WHERE book.PublishedYear BETWEEN :year_from AND :year_to;
60 ");
61 $stmt->bindParam(":year_from", $query_from, PDO::PARAM_STR);
62 $stmt->bindParam(":year_to", $query_to, PDO::PARAM_STR);
63 $stmt->execute();
64 }
65
66 if ($year_from !== null) {
67 $where_conditions[] = "book.publishedyear >= ?";
68 $params[] = $year_from;
69 }
70 if ($year_to !== null) {
71 $where_conditions[] = "book.publishedyear <= ?";
72 $params[] = $year_to;
73 }
74
75 $all_books = [];
76
77 while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
78 $all_books[] = $row;
79 }
80
81 // CART
82 if(isset($_GET['submit']) && isset($_GET['bookid'])){
83
84 if(!isset($_SESSION['userid'])) {
85 header("Location: ./SignLog.php");
86 die();
87 }
88
89 $sql = "SELECT * FROM member WHERE memberid = :userid";
90 $stmt = $conn->prepare($sql);
91 $stmt->bindParam(':userid', $_SESSION['userid'], PDO::PARAM_INT);
92 $stmt->execute();
93
94 $row = $stmt->fetch(PDO::FETCH_ASSOC);
95 $current_date = date('Y-m-d');
96
97 if($stmt->rowCount() <= 0 || $row['expired_date'] < $current_date || $row['membership_status'] != 'Active') {
98 // ne si member ili ti istekol ili ti e neaktiven ili suspendiran
99 header("Location: ./Profile.php");
100 die();
101 }
102
103 if ($_GET['submit'] == 'add-to-cart') {
104 try {
105 $check_stmt = $conn->prepare("
106 SELECT Cart.*, member.*, book.* FROM CART
107 INNER JOIN Member ON Cart.memberid = member.memberid
108 INNER JOIN Book ON Cart.bookid = Book.bookid
109 WHERE Book.bookid = :bookid AND member.memberid = {$row['memberid']};
110 ");
111
112 $check_stmt->bindParam(':bookid', $_GET['bookid'], PDO::PARAM_INT);
113
114 $check_stmt->execute();
115
116 if($check_stmt->rowCount() > 0){
117 header("Location: ./Cart.php");
118 die();
119 }
120 else {
121 // INSERT INTO CART
122 $sql = $conn->prepare('INSERT INTO Cart (BookID, MemberID) VALUES (:book_id, :member_id)');
123 $sql->bindParam(':book_id', $_GET['bookid'], PDO::PARAM_INT);
124 $sql->bindParam(':member_id', $row['memberid'], PDO::PARAM_INT);
125 $sql->execute();
126 }
127
128 header("Location: ./Cart.php");
129 exit();
130
131 } catch (PDOException $e) {
132 error_log("Database error: " . $e->getMessage());
133 echo $e->getMessage();
134 echo "An error occurred while adding the book to your cart.";
135 exit();
136 }
137 }
138 header("Location: ./Cart.php");
139 }
140?>
141
142<!DOCTYPE html>
143<html lang="en">
144<head>
145 <meta charset="UTF-8">
146 <meta name="viewport" content="width=device-width, initial-scale=1.0">
147 <title>All Books</title>
148 <link rel="stylesheet" href="CSS/Books.css">
149
150</head>
151<body>
152 <?php include 'Components/Header.html'; ?>
153
154
155 <main class="books-page">
156 <!-- Filters Sidebar -->
157
158 <aside class="filters-sidebar">
159 <div class="filters-header">
160 <h2>Filters</h2>
161 <button class="clear-filters">Clear All</button>
162 </div>
163
164 <!-- Categories Filter -->
165 <form action="./Books.php" method="GET">
166 <div class="filter-section">
167 <h3>Genre</h3>
168 <div class="filter-options">
169 <?php
170 foreach($distinct_genres as $gen) {
171 echo "<label class='filter-option'>
172 <input type='checkbox' name='genres[]' ".((in_array($gen, $genres_filters)) ? "checked" : "")." value='$gen'> $gen
173 </label>";
174 }
175 ?>
176 </div>
177 </div>
178
179 <!-- Publication Year Filter -->
180 <div class="filter-section">
181 <h3>Publication Year</h3>
182 <div class="filter-options">
183 <select id="yearFrom" name="year_from">
184 <option value="<?php echo $year_min?>">From</option>
185 <?php
186 for($year = $year_range['max_year']; $year >= $year_range['min_year']; $year--) {
187 $selected = ($year_from == $year) ? 'selected' : '';
188 echo "<option value='$year' $selected>$year</option>";
189 }
190 ?>
191 </select>
192 <select id="yearTo" name="year_to">
193 <option value="<?php echo $year_max ?>">To</option>
194 <?php
195 for($year = $year_range['max_year']; $year >= $year_range['min_year']; $year--) {
196 $selected = ($year_to == $year) ? 'selected' : '';
197 echo "<option value='$year' $selected>$year</option>";
198 }
199 ?>
200 </select>
201 </div>
202 </div>
203 <button type="submit" class="btn-btn-primary">Apply Filters</button>
204 </form>
205 </aside>
206
207 <!-- Books Content -->
208 <section class="books-content">
209 <!-- Top Bar -->
210 <div class="books-header">
211 <div class="results-count">
212 <span id="bookCount">0</span> Results
213 </div>
214 <div class="sort-options">
215 <label for="sortBy">Sort by:</label>
216 <select id="sortBy">
217 <option value="relevance">Relevance</option>
218 <option value="name-asc">Name: A to Z</option>
219 <option value="name-desc">Name: Z to A</option>
220 <option value="year-desc">Newest to Oldest</option>
221 <option value="year-asc">Oldest to Newest</option>
222 </select>
223 </div>
224 </div>
225
226 <!-- Books Grid -->
227 <div class="books-grid" id="booksGrid">
228 <!-- Books will be dynamically added here -->
229 </div>
230
231 <!-- Pagination -->
232 <div class="pagination">
233 <button class="pagination-button" id="prevPage">Previous</button>
234 <div class="page-numbers" id="pageNumbers">
235 Page numbers will be dynamically added here
236 </div>
237 <button class="pagination-button" id="nextPage">Next</button>
238 </div>
239
240 </section>
241</main>
242
243
244<script>
245const allBooks = [
246 <?php
247 foreach ($all_books as $book) {
248 echo "{id: " . htmlspecialchars($book['bookid'], ENT_QUOTES, 'UTF-8') .
249 ", title: '" . htmlspecialchars($book['title'], ENT_QUOTES, 'UTF-8') .
250 "', author: '" . htmlspecialchars($book['firstname'] . " " . $book['lastname'], ENT_QUOTES, 'UTF-8') .
251 "', image: '" . htmlspecialchars($book['coverimage'] ?? '', ENT_QUOTES, 'UTF-8') .
252 "', genre: '" . htmlspecialchars($book['genre'], ENT_QUOTES, 'UTF-8') .
253 "', publishedYear: " . intval($book['publishedyear']) . "},";
254 }
255 ?>
256];
257
258 // State management
259 let currentState = {
260 filters: {
261 genres: <?php echo json_encode($distinct_genres) ?>,
262 yearFrom: <?php echo $year_from ? $year_from : 'null' ?>,
263 yearTo: <?php echo $year_to ? $year_to : 'null' ?>
264 },
265 sort: 'relevance',
266 page: 1,
267 itemsPerPage: 15,
268 };
269
270const pages = document.getElementById('pageNumbers');
271
272// Initialize page
273document.addEventListener('DOMContentLoaded', () => {
274
275 const sortedBooks = sortBooks(allBooks, currentState.sort);
276 allBooks.length = 0;
277 allBooks.push(...sortedBooks);
278
279 document.getElementById('nextPage').addEventListener('click', goToNextPage);
280 document.getElementById('prevPage').addEventListener('click', goToPrevPage);
281
282 currentState.page = 1;
283
284 initializeGenreDisplay();
285 renderPagination();
286});
287
288
289// Render books
290function renderBook(cur_book) {
291 // Render books
292 const card_hidden = document.createElement('input');
293 const card_element = document.createElement('form');
294 const card_image = document.createElement('img');
295 const card_info = document.createElement('div');
296 const card_title = document.createElement('h3');
297 const card_author = document.createElement('p');
298 const card_genre = document.createElement('p');
299 const actions_div = document.createElement('div');
300 const actions_button = document.createElement('button');
301
302 actions_button.innerHTML = "Add to cart";
303 actions_div.appendChild(actions_button);
304
305 card_image.src = "./BookImages/" + cur_book.image;
306 card_image.alt = cur_book.title;
307 //card_title.innerHTML = cur_book.title;
308 card_author.innerHTML = "by " + cur_book.author;
309 card_genre.innerHTML = cur_book.genre;
310
311 const titleLink = document.createElement('a');
312 titleLink.href = `BookView.php?bookid=${cur_book.id}`;
313 titleLink.textContent = cur_book.title;
314 titleLink.style.textDecoration = 'none';
315 titleLink.style.color = 'green';
316 card_title.appendChild(titleLink);
317
318 card_info.appendChild(card_title);
319 card_info.appendChild(card_author);
320 card_info.appendChild(card_genre);
321
322 card_element.appendChild(card_image);
323 card_element.appendChild(card_info);
324 card_element.appendChild(actions_div);
325 card_element.appendChild(card_hidden);
326
327 card_element.classList.add('book-card');
328 card_image.classList.add('book-cover');
329 card_info.classList.add('book-info');
330 card_title.classList.add('book-title');
331 card_author.classList.add('book-author');
332 card_genre.classList.add('book-genre');
333 actions_div.classList.add('actions');
334 actions_button.classList.add('btn', 'btn-primary');
335 actions_button.type = 'submit';
336 actions_button.value = 'add-to-cart';
337 actions_button.name = 'submit';
338
339 card_element.setAttribute('action', './Books.php');
340 card_element.setAttribute('method', 'GET');
341
342 card_hidden.setAttribute('type', 'hidden');
343 card_hidden.setAttribute('name', 'bookid');
344 card_hidden.setAttribute('value', cur_book.id);
345
346
347 booksGrid.appendChild(card_element)
348
349}
350
351function renderPagination() {
352
353 booksGrid.innerHTML = "";
354 document.getElementById('bookCount').textContent = allBooks.length;
355
356 // render books
357 for(let i = (currentState.page-1)*currentState.itemsPerPage; i < Math.min(currentState.page*currentState.itemsPerPage, allBooks.length); i++) {
358 renderBook(allBooks[i]);
359 }
360 // Disable/enable prev/next buttons
361 document.getElementById('prevPage').disabled = currentState.page === 1;
362 document.getElementById('nextPage').disabled = currentState.page === Math.ceil(allBooks.length / currentState.itemsPerPage);
363
364 pages.innerHTML = "";
365
366 for(let i = 0; i < allBooks.length / currentState.itemsPerPage; i++) {
367 const page_number = document.createElement('button');
368 page_number.classList.add('page-button');
369 if(i+1 === currentState.page) {
370 page_number.classList.add('active');
371 }
372 page_number.textContent = i+1;
373 page_number.addEventListener('click', () => {
374 currentState.page = i+1;
375 renderPagination()
376 });
377 pages.appendChild(page_number);
378 }
379}
380
381// Clear filters
382 document.querySelector('.clear-filters').addEventListener('click', (e) => {
383 // Reset filters in the state
384 currentState.filters = {
385 yearFrom: null,
386 yearTo: null,
387 genres: []
388 };
389
390 // Reset checkboxes
391 document.querySelectorAll('.filter-option input[type="checkbox"]').forEach(checkbox => {
392 checkbox.checked = false;
393 });
394
395 // Reset year dropdowns
396 document.getElementById('yearFrom').value = '';
397 document.getElementById('yearTo').value = '';
398
399 document.querySelector('form').submit();
400 });
401
402
403 function sortBooks(books, sortType) {
404 const sortedBooks = [...books];
405
406 switch(sortType) {
407 case 'name-asc':
408 sortedBooks.sort((a, b) => a.title.localeCompare(b.title));
409 break;
410 case 'name-desc':
411 sortedBooks.sort((a, b) => b.title.localeCompare(a.title));
412 break;
413 case 'year-desc':
414 sortedBooks.sort((a, b) => b.publishedYear - a.publishedYear);
415 break;
416 case 'year-asc':
417 sortedBooks.sort((a, b) => a.publishedYear - b.publishedYear);
418 break;
419 case 'relevance':
420 default:
421 break;
422 }
423
424 return sortedBooks;
425}
426
427document.getElementById('sortBy').addEventListener('change', (e) => {
428 currentState.sort = e.target.value;
429
430 const sortedBooks = sortBooks(allBooks, currentState.sort);
431
432 allBooks.length = 0;
433 allBooks.push(...sortedBooks);
434
435 currentState.page = 1;
436
437 renderPagination();
438});
439
440function goToNextPage() {
441 const maxPages = Math.ceil(allBooks.length / currentState.itemsPerPage);
442 if (currentState.page < maxPages) {
443 currentState.page++;
444 renderPagination();
445 }
446}
447
448function goToPrevPage() {
449 if (currentState.page > 1) {
450 currentState.page--;
451 renderPagination();
452 }
453}
454
455function initializeGenreDisplay() {
456 const GENRES_PER_LOAD = 5;
457 const filterOptions = document.querySelector('.filter-options');
458 const allGenreLabels = Array.from(filterOptions.querySelectorAll('.filter-option'));
459
460 // Hide all genres initially
461 allGenreLabels.forEach((label, index) => {
462 if (index >= GENRES_PER_LOAD) {
463 label.style.display = 'none';
464 }
465 });
466
467 // Only add Show More button if there are more genres to show
468 if (allGenreLabels.length > GENRES_PER_LOAD) {
469 const showMoreButton = document.createElement('button');
470 showMoreButton.setAttribute('type', 'button');
471 showMoreButton.textContent = 'Show More';
472 showMoreButton.classList.add('show-more-btn');
473 filterOptions.appendChild(showMoreButton);
474
475 let currentlyShown = GENRES_PER_LOAD;
476
477 showMoreButton.addEventListener('click', () => {
478 for (let i = currentlyShown; i < Math.min(currentlyShown + GENRES_PER_LOAD, allGenreLabels.length); i++) {
479 allGenreLabels[i].style.display = 'block';
480 }
481
482 currentlyShown += GENRES_PER_LOAD;
483
484 if (currentlyShown >= allGenreLabels.length) {
485 showMoreButton.style.display = 'none';
486 }
487 });
488 }
489}
490
491 const styleSheet = document.createElement('style');
492 styleSheet.textContent = `
493 .show-more-btn {
494 margin-top: 10px;
495 padding: 5px 10px;
496 background: none;
497 border: 1px solid #ccc;
498 border-radius: 4px;
499 cursor: pointer;
500 width: 100%;
501 text-align: center;
502 }
503
504 .show-more-btn:hover {
505 background-color: #f5f5f5;
506 }
507
508 .filter-options {
509 display: flex;
510 flex-direction: column;
511 gap: 8px;
512 }
513 `;
514 document.head.appendChild(styleSheet);
515
516
517</script>
518
519<?php include 'Components/Footer.html'; ?>
520</body>
521</html>
Note: See TracBrowser for help on using the repository browser.