From 938b914bb0ea9b3ed578cb2b5def3a2fcef9740e Mon Sep 17 00:00:00 2001
From: darynaansimova <96005019+darynaansimova@users.noreply.github.com>
Date: Wed, 25 Jun 2025 23:01:35 +0300
Subject: [PATCH] Lab04, 05
---
data/books.json | 90 +++++++++++++++
src/index.html | 41 +++++++
src/main.js | 211 ++++++++++++++++++++++++++++++++++
src/report.html | 24 ++++
src/report.js | 74 ++++++++++++
style/style.css | 300 ++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 740 insertions(+)
create mode 100644 data/books.json
create mode 100644 src/index.html
create mode 100644 src/report.html
create mode 100644 src/report.js
diff --git a/data/books.json b/data/books.json
new file mode 100644
index 0000000..ee6003d
--- /dev/null
+++ b/data/books.json
@@ -0,0 +1,90 @@
+[
+ {
+ "title": "The Great Gatsby",
+ "author": "F. Scott Fitzgerald",
+ "year": 1925,
+ "genre": "Fiction",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/8221256-L.jpg"
+ },
+ {
+ "title": "To Kill a Mockingbird",
+ "author": "Harper Lee",
+ "year": 1960,
+ "genre": "Fiction",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/10470884-L.jpg"
+ },
+ {
+ "title": "1984",
+ "author": "George Orwell",
+ "year": 1949,
+ "genre": "Dystopian",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/11141570-L.jpg"
+ },
+ {
+ "title": "Pride and Prejudice",
+ "author": "Jane Austen",
+ "year": 1813,
+ "genre": "Romance",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/8231996-L.jpg"
+ },
+ {
+ "title": "Кобзар",
+ "author": "Тарас Шевченко",
+ "year": 1840,
+ "genre": "Poetry",
+ "language": "ua",
+ "image": "https://covers.openlibrary.org/b/id/10579826-L.jpg"
+ },
+ {
+ "title": "Місто",
+ "author": "Валер'ян Підмогильний",
+ "year": 1928,
+ "genre": "Modernism",
+ "language": "ua",
+ "image": "https://covers.openlibrary.org/b/id/10579826-L.jpg"
+ },
+ {
+ "title": "Brave New World",
+ "author": "Aldous Huxley",
+ "year": 1932,
+ "genre": "Dystopian",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/10579790-L.jpg"
+ },
+ {
+ "title": "The Alchemist",
+ "author": "Paulo Coelho",
+ "year": 1988,
+ "genre": "Adventure",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/8231843-L.jpg"
+ },
+ {
+ "title": "Les Misérables",
+ "author": "Victor Hugo",
+ "year": 1862,
+ "genre": "Historical",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/8319256-L.jpg"
+ },
+ {
+ "title": "Захар Беркут",
+ "author": "Іван Франко",
+ "year": 1883,
+ "genre": "Historical",
+ "language": "ua",
+ "image": "https://covers.openlibrary.org/b/id/10579830-L.jpg"
+ },
+ {
+ "title": "The Catcher in the Rye",
+ "author": "J.D. Salinger",
+ "year": 1951,
+ "genre": "Fiction",
+ "language": "en",
+ "image": "https://covers.openlibrary.org/b/id/8221256-L.jpg"
+}
+]
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..855cd80
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ Online Book Library
+
+
+
+
+
+
+
+
+ Online Book Library
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
index e69de29..ffd7ebb 100644
--- a/src/main.js
+++ b/src/main.js
@@ -0,0 +1,211 @@
+let books = [];
+let currentSort = null;
+let favorites = new Set();
+
+function loadFavorites() {
+ const saved = localStorage.getItem('bookFavorites');
+ if (saved) favorites = new Set(JSON.parse(saved));
+}
+
+function saveFavorites() {
+ localStorage.setItem('bookFavorites', JSON.stringify([...favorites]));
+}
+// Add this to your DOMContentLoaded event
+document.addEventListener('DOMContentLoaded', async () => {
+ loadFavorites();
+ try {
+ books = await fetchBooks();
+ renderBooks(books);
+ buildFilters(books);
+ setupFilterHandlers();
+ setupFavoritesButton();
+ setupSortHandlers();
+ setupFilterToggle();
+ } catch (error) {
+ console.error('Error initializing application:', error);
+ showError('Failed to load books. Please try again later.');
+ }
+});
+
+function setupFilterToggle() {
+ const toggle = document.querySelector('.filters-toggle');
+ const filters = document.querySelector('.filters');
+ if (!toggle || !filters) return;
+
+ const updateDisplay = () => {
+ const wide = window.innerWidth > 1024;
+ filters.style.display = wide ? 'flex' : 'none';
+ toggle.style.display = wide ? 'none' : 'inline-block';
+ toggle.textContent = wide ? '' : 'Filter';
+ };
+
+ updateDisplay();
+ window.addEventListener('resize', updateDisplay);
+
+ toggle.addEventListener('click', () => {
+ if (window.innerWidth > 1024) return;
+ const visible = filters.style.display === 'flex';
+ filters.style.display = visible ? 'none' : 'flex';
+ toggle.textContent = visible ? 'Filter' : 'Hide Filters';
+ });
+}
+
+async function fetchBooks() {
+ const res = await fetch('../data/books.json');
+ return res.json();
+}
+
+function renderBooks(list) {
+ const container = document.querySelector('.book-grid');
+ container.innerHTML = '';
+ list.forEach(book => {
+ const card = document.createElement('div');
+ card.className = 'book-card';
+ card.innerHTML = `
+
+
+
${book.title}
+
${book.author}
+
+ `;
+ card.addEventListener('click', () => showPopup(book));
+ container.appendChild(card);
+ });
+}
+
+function buildFilters(data) {
+ const genres = [...new Set(data.map(b => b.genre))];
+ const years = [...new Set(data.map(b => b.year))].sort();
+ const languages = [...new Set(data.map(b => b.language))];
+ const section = document.querySelector('.filters > div');
+
+ const checkboxes = (items, prefix) => items
+ .map(val => `
+
`).join('');
+
+ section.innerHTML = `
+ Genre:
${checkboxes(genres, 'g')}
+ Author:
+ Year:
${checkboxes(years, 'y')}
+ Language:
${checkboxes(languages, 'l')}
+ `;
+}
+
+function setupFilterHandlers() {
+ document.querySelector('.filters').addEventListener('input', applyFilters);
+}
+
+function applyFilters() {
+ const q = sel => [...document.querySelectorAll(sel)];
+ const genre = q('input[id^="g-"]:checked').map(i => i.value);
+ const year = q('input[id^="y-"]:checked').map(i => +i.value);
+ const lang = q('input[id^="l-"]:checked').map(i => i.value);
+ const author = document.getElementById('author').value.toLowerCase();
+
+ let result = books.filter(b =>
+ genre.includes(b.genre) &&
+ (year.length === 0 || year.includes(b.year)) &&
+ lang.includes(b.language) &&
+ b.author.toLowerCase().includes(author)
+ );
+
+ if (currentSort) {
+ result.sort((a, b) =>
+ currentSort === 'year'
+ ? a.year - b.year
+ : a[currentSort].localeCompare(b[currentSort])
+ );
+ }
+
+ if (document.getElementById('favorites-btn').innerText === 'Back to All') {
+ result = result.filter(b => favorites.has(b.title));
+ }
+
+ renderBooks(result);
+}
+
+function setupSortHandlers() {
+ ['sort1', 'sort2'].forEach(id => {
+ const btn = document.getElementById(id);
+ if (!btn) return;
+ btn.dataset.sort = '';
+ btn.innerHTML = id === 'sort1' ? 'Sort' : 'Sort by:';
+
+ btn.addEventListener('click', () => {
+ const opts = ['year', 'genre'];
+ const current = btn.dataset.sort || 'year';
+ const next = opts[(opts.indexOf(current) + 1) % opts.length];
+ currentSort = next;
+ btn.dataset.sort = next;
+ document.getElementById('sort1').innerHTML = next.charAt(0).toUpperCase() + next.slice(1);
+ document.getElementById('sort2').innerHTML = `Sort by: ${next.charAt(0).toUpperCase() + next.slice(1)}`;
+ applyFilters();
+ });
+ });
+}
+
+function showPopup(book) {
+ const modal = document.createElement('div');
+ modal.className = 'popup';
+ modal.innerHTML = `
+
+ `;
+ document.body.appendChild(modal);
+
+ modal.querySelector('#fav-btn').addEventListener('click', () => {
+ favorites[favorites.has(book.title) ? 'delete' : 'add'](book.title);
+ saveFavorites();
+ document.body.removeChild(modal);
+ document.getElementById('favorites-btn').innerText === 'Back to All'
+ ? renderBooks(books.filter(b => favorites.has(b.title)))
+ : renderBooks(books);
+ });
+
+ modal.querySelector('#close-btn').addEventListener('click', () => {
+ document.body.removeChild(modal);
+ });
+}
+
+function setupFavoritesButton() {
+ const btn = document.createElement('button');
+ btn.id = 'favorites-btn';
+ Object.assign(btn.style, {
+ position: 'fixed',
+ bottom: '20px',
+ right: '20px',
+ padding: '10px 20px',
+ background: '#f3f4f6',
+ border: '1px solid #ccc',
+ borderRadius: '5px',
+ cursor: 'pointer'
+ });
+ btn.innerText = 'Favorites';
+ document.body.appendChild(btn);
+
+ btn.addEventListener('click', () => {
+ document.getElementById('sort2').innerHTML = 'Sort by:';
+ document.getElementById('sort1').innerHTML = 'Sort';
+ if (btn.innerText === 'Back to All') {
+ btn.innerText = 'Favorites';
+ renderBooks(books);
+ resetFilters();
+ } else {
+ btn.innerText = 'Back to All';
+ renderBooks(books.filter(b => favorites.has(b.title)));
+ resetFilters();
+ }
+ });
+}
+
+function resetFilters() {
+ document.querySelectorAll('.filters input[type="checkbox"]').forEach(i => (i.checked = true));
+ document.getElementById('author').value = '';
+}
\ No newline at end of file
diff --git a/src/report.html b/src/report.html
new file mode 100644
index 0000000..cc922c1
--- /dev/null
+++ b/src/report.html
@@ -0,0 +1,24 @@
+
+
+
+
+ Аналітика книг
+
+
+
+
+
+
+
+
+ Завантаження даних...
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/report.js b/src/report.js
new file mode 100644
index 0000000..564a9a2
--- /dev/null
+++ b/src/report.js
@@ -0,0 +1,74 @@
+document.addEventListener('DOMContentLoaded', async () => {
+ try {
+ const loadingMessage = document.getElementById('loading-message');
+ loadingMessage.textContent = 'Завантаження даних...';
+
+ const response = await fetch('../data/books.json');
+
+ if (!response.ok) {
+ throw new Error(`Помилка завантаження: ${response.status}`);
+ }
+
+ const books = await response.json();
+
+ const reportData = books.map(book => ({
+ Назва: book.title,
+ Автор: book.author,
+ Рік: book.year,
+ Жанр: book.genre,
+ Мова: book.language,
+ Зображення: book.image
+ }));
+
+ new WebDataRocks({
+ container: "#wdr-component",
+ toolbar: true,
+ report: {
+ dataSource: {
+ data: reportData,
+ dataSourceType: 'json'
+ },
+ slice: {
+ rows: [
+ { uniqueName: "Назва" },
+ { uniqueName: "Автор" },
+ { uniqueName: "Рік" },
+ { uniqueName: "Жанр" },
+ { uniqueName: "Мова" },
+ { uniqueName: "Зображення" }
+ ],
+ measures: [
+ {
+ uniqueName: "Назва",
+ aggregation: "count",
+ caption: "Назва"
+ }
+ ]
+ },
+ options: {
+ grid: {
+ type: "flat",
+ showTotals: "off",
+ showGrandTotals: "off"
+ }
+ }
+ },
+ global: {
+ localization: "https://cdn.webdatarocks.com/loc/uk.json"
+ }
+ });
+
+ loadingMessage.style.display = 'none';
+
+ } catch (error) {
+ console.error('Помилка:', error);
+ const loadingMessage = document.getElementById('loading-message');
+ loadingMessage.innerHTML = `
+
+ `;
+ loadingMessage.classList.add('error-message');
+ }
+});
\ No newline at end of file
diff --git a/style/style.css b/style/style.css
index e69de29..038ff03 100644
--- a/style/style.css
+++ b/style/style.css
@@ -0,0 +1,300 @@
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background-color: white;
+ color: #1f2937;
+ line-height: 1.6;
+ margin: 0rem
+}
+
+header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 2rem;
+ background-color: white;
+ border-top: 0;
+ border-bottom: 1px solid #e5e7eb;
+}
+
+header h1 {
+ font-size: 1.8rem;
+ color: #111827;
+}
+
+button {
+ background-color: white;
+ border: 1px solid #d1d5db;
+ padding: 0.5rem 1rem;
+ border-radius: 0.375rem;
+ cursor: pointer;
+ font-size: 0.95rem;
+ color: #374151;
+ margin: 0.5rem 0.2rem;
+}
+
+header button {
+ display: block;
+ font-weight: 500;
+ color: #374151;
+ padding: 0.5rem 1rem;
+ border: 1px solid #d1d5db;
+ border-radius: 0.375rem;
+ background-color: white;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.95rem;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ padding: 1rem;
+}
+
+.filters {
+ display: none;
+ flex-direction: column;
+ gap: 1rem;
+ background-color: #fcfcfc;
+ padding: 1rem;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ margin-bottom: 1rem;
+}
+
+.filters h3 {
+ font-size: 1.25rem;
+ margin-bottom: 0.5rem;
+}
+
+.filters h4 {
+ margin-top: 1rem;
+ font-weight: 600;
+}
+
+.filters input[type="text"] {
+ padding: 0.4rem;
+ border: 1px solid #d1d5db;
+ border-radius: 4px;
+ margin-top: 0.25rem;
+ width: 100%;
+}
+
+#sort2 {
+ display: none;
+}
+
+
+.book-section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin: 1rem 1rem;
+}
+
+.book-section-header h3 {
+ font-size: 1.7rem;
+ color: #1f2937;
+}
+
+.book-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1rem;
+}
+
+.book-card {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ background-color: white;
+ border-radius: 8px;
+ padding: 0.75rem;
+ gap: 1rem;
+}
+
+.book-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
+}
+
+.book-card img {
+ width: 7rem;
+ height: auto;
+ border-radius: 4px;
+ flex-shrink: 0;
+}
+
+.book-card div {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: 0.2rem;
+}
+
+.book-card h4 {
+ font-size: 1rem;
+ color: #111827;
+ font-weight: 600;
+}
+
+.book-card p {
+ font-size: 0.875rem;
+ margin-top: 2px;
+}
+
+.popup {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.4);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 999;
+}
+
+.popup-content {
+ background-color: white;
+ padding: 2rem;
+ border-radius: 10px;
+ width: 300px;
+ box-shadow: 0 0 10px rgba(0,0,0,0.3);
+}
+
+
+@media (min-width: 600px) {
+ body{
+ margin: 1rem;
+ border: 1px solid #e5e7eb;
+ border-radius: 15px;
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
+ }
+ header {
+ border-top-left-radius: 15px;
+ border-top-right-radius: 15px;
+ }
+ header button{
+ border: none
+ }
+ .container{
+ border-bottom-left-radius: 15px;
+ border-bottom-right-radius: 15px;
+ }
+ #sort1::after{
+ content: '▾';
+ font-size: 1rem;
+ color: #4b5563;
+ margin-left: 0.2rem;
+ }
+
+ .book-section-header h3 {
+ font-size: 2rem;
+ }
+ .book-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .book-card {
+ flex-direction: column;
+ align-items: flex-start;
+ text-align: left;
+ }
+
+ .book-card img {
+ width: 100%;
+ margin-bottom: 0.5rem;
+ }
+
+ .book-card div {
+ align-items: flex-start;
+ }
+
+ #sort2 {
+ display: none;
+ }
+}
+
+@media (min-width: 1024px) {
+ body{
+ margin: 0;
+ border-radius: 0;
+ }
+ header {
+ border-radius: 0;
+ border-top:0;
+ background-color: #fcfcfc;
+}
+header button{
+ background-color: #fcfcfc;
+}
+
+ header button:hover {
+ background-color: #e4e6e9;
+}
+ .book-section-header h3 {
+ font-size: 1.5rem;
+ }
+ .container {
+ flex-direction: row;
+ gap: 2rem;
+ }
+
+ aside {
+ width: 250px;
+ }
+
+ .filters-toggle {
+ display: none;
+ }
+
+ .filters {
+ display: flex;
+ margin-bottom: 0;
+ }
+
+ #sort1 {
+ display: none;
+ }
+
+ #sort2 {
+ display: block;
+ font-weight: 500;
+ color: #374151;
+ padding: 0.5rem 1rem;
+ border: 1px solid #d1d5db;
+ border-radius: 0.375rem;
+ background-color: white;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.95rem;
+ cursor: pointer;
+}
+
+#sort2::after {
+ content: '▾';
+ font-size: 1rem;
+ color: #4b5563;
+ margin-left: 1rem;
+}
+
+ .book-grid {
+ grid-template-columns: repeat(4, 1fr);
+ }
+
+ .book-card {
+ padding: 0.75rem;
+ }
+}