Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions data/books.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
41 changes: 41 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Online Book Library</title>
<link rel="stylesheet" href="../style/style.css" />
<link href="https://cdn.webdatarocks.com/latest/webdatarocks.min.css" rel="stylesheet"/>
<script src="https://cdn.webdatarocks.com/latest/webdatarocks.toolbar.min.js"></script>
<script src="https://cdn.webdatarocks.com/latest/webdatarocks.js"></script>
<script src="main.js" defer></script>
</head>
<body>
<header>
<h1>Online Book Library</h1>
<div>
<button class="filters-toggle">Filter</button>
<button class="sort" id="sort1">Sort</button>
</div>
</header>

<main class="container">
<aside>
<div class="filters">
<h3>Filter</h3>
<div>
</div>
</div>
</aside>
<section class="book-section">
<div class="book-section-header">
<h3>Featured</h3>
<div class="sort" id="sort2">Sort by:</div>
</div>
<section class="book-grid">
</section>
</section>
</main>
</body>
<button onclick="window.location.href='report.html'">Переглянути звіт</button>
</html>
211 changes: 211 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -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 = `
<img src="${book.image}" alt="${book.title}">
<div>
<h4>${book.title}</h4>
<p>${book.author}</p>
</div>
`;
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 => `<input type="checkbox" id="${prefix}-${val}" value="${val}" checked>
<label for="${prefix}-${val}">${val}</label><br>`).join('');

section.innerHTML = `
<h4>Genre:</h4>${checkboxes(genres, 'g')}
<h4>Author:</h4><input type="text" id="author" placeholder="Enter author">
<h4>Year:</h4>${checkboxes(years, 'y')}
<h4>Language:</h4>${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 = `
<div class="popup-content">
<h2>${book.title}</h2>
<p><strong>Author:</strong> ${book.author}</p>
<p><strong>Genre:</strong> ${book.genre}</p>
<p><strong>Year:</strong> ${book.year}</p>
<p><strong>Language:</strong> ${book.language}</p>
<button id="fav-btn">${favorites.has(book.title) ? 'Remove from' : 'Add to'} Favorites</button>
<button id="close-btn">Close</button>
</div>
`;
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 = '';
}
24 changes: 24 additions & 0 deletions src/report.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8" />
<title>Аналітика книг</title>
<link rel="stylesheet" href="../style/style.css">
<link href="https://cdn.webdatarocks.com/latest/webdatarocks.min.css" rel="stylesheet"/>
</head>
<body>
<header class="analytics-header">
<h1>Аналітика книг</h1>
<a href="index.html" class="back-btn">← Назад до бібліотеки</a>
</header>

<main class="container">
<div id="wdr-component"></div>
<div id="loading-message">Завантаження даних...</div>
</main>

<script src="https://cdn.webdatarocks.com/latest/webdatarocks.toolbar.min.js"></script>
<script src="https://cdn.webdatarocks.com/latest/webdatarocks.js"></script>
<script src="report.js"></script>
</body>
</html>
Loading