estante-digital / index.html
emroberto's picture
retire os acentos da planilha exportada - Initial Deployment
a81ee98 verified
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📚 Estante Digital</title>
<link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📚</text></svg>">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#4f46e5',
secondary: '#f9fafb',
accent: '#f3f4f6'
},
fontFamily: {
sans: ['Inter', 'sans-serif']
}
}
}
}
</script>
<style>
.book-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.form-input:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2);
}
</style>
</head>
<body class="bg-secondary min-h-screen font-sans">
<div class="max-w-6xl mx-auto px-4 py-8">
<!-- Header -->
<header class="mb-12 text-center py-8 bg-white rounded-xl shadow-sm">
<h1 class="text-4xl md:text-5xl font-bold text-gray-800 flex items-center justify-center gap-3">
<span class="text-5xl">📚</span> Estante Digital
</h1>
<p class="mt-3 text-gray-600 max-w-2xl mx-auto">
Gerencie os livros de história
</p>
</header>
<!-- Search Section -->
<section class="mb-16 bg-white rounded-xl shadow-sm p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-6 pb-2 border-b">Localizar Livros</h2>
<form id="searchForm" class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-gray-700 font-medium mb-2" for="searchTitle">Título do Livro</label>
<input
type="text"
id="searchTitle"
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Digite o título do livro">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="searchAuthor">Autor</label>
<input
type="text"
id="searchAuthor"
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Nome do autor">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="searchPublisher">Editora</label>
<input
type="text"
id="searchPublisher"
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Nome da editora">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="searchYear">Ano de Publicação</label>
<input
type="number"
id="searchYear"
min="1000"
max="2024"
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Ano de publicação">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="searchRoom">Sala</label>
<input
type="text"
id="searchRoom"
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Número ou nome da sala">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="searchCabinet">Armário</label>
<select
id="searchCabinet"
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200">
<option value="">Todos os armários</option>
<option value="Armário 1">Armário 1</option>
<option value="Armário 2">Armário 2</option>
<option value="Armário 3">Armário 3</option>
<option value="Armário 4">Armário 4</option>
<option value="Armário 5">Armário 5</option>
</select>
</div>
<div class="md:col-span-2 flex gap-3 mt-4">
<button
type="submit"
class="flex-1 px-6 py-3 bg-primary hover:bg-indigo-700 text-white font-medium rounded-lg shadow-md transition duration-300 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
🔍 Pesquisar Livros
</button>
<button
type="button"
id="clearSearch"
class="px-6 py-3 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium rounded-lg shadow-md transition duration-300 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-300">
Limpar
</button>
</div>
</form>
<div class="mt-4 text-center text-sm text-gray-500">
developed by Vitão
</div>
</section>
<!-- Form Section -->
<section class="mb-16 bg-white rounded-xl shadow-sm p-6">
<div class="border-b border-gray-200">
<button id="toggleForm" class="flex items-center justify-between w-full py-4 text-left font-medium text-gray-800 hover:text-primary transition-colors duration-200">
<span class="text-2xl font-semibold">Cadastro de Livros</span>
<svg id="formIcon" class="w-5 h-5 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
</div>
<form id="bookForm" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6 hidden">
<div>
<label class="block text-gray-700 font-medium mb-2" for="title">Título do Livro</label>
<input
type="text"
id="title"
required
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Digite o título do livro">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="author">Autor</label>
<input
type="text"
id="author"
required
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Nome do autor">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="publisher">Editora</label>
<input
type="text"
id="publisher"
required
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Nome da editora">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="year">Ano de Publicação</label>
<input
type="number"
id="year"
min="1000"
max="2024"
required
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Ano de publicação">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="room">Sala</label>
<input
type="text"
id="room"
required
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
placeholder="Número ou nome da sala">
</div>
<div>
<label class="block text-gray-700 font-medium mb-2" for="cabinet">Armário</label>
<select
id="cabinet"
required
class="form-input w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary transition duration-200">
<option value="">Selecione um armário</option>
<option value="Armário 1">Armário 1</option>
<option value="Armário 2">Armário 2</option>
<option value="Armário 3">Armário 3</option>
<option value="Armário 4">Armário 4</option>
<option value="Armário 5">Armário 5</option>
</select>
</div>
<div class="md:col-span-2 mt-4">
<button
type="submit"
class="w-full md:w-auto px-6 py-3 bg-primary hover:bg-indigo-700 text-white font-medium rounded-lg shadow-md transition duration-300 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
📖 Salvar Livro
</button>
</div>
</form>
<div class="mt-4 text-center text-sm text-gray-500">
developed by Vitão
</div>
</section>
<!-- Books List Section -->
<section class="mb-16">
<h2 class="text-2xl font-semibold text-gray-800 mb-6 pb-2 border-b">Livros Cadastrados</h2>
<div id="booksList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Book cards will be inserted here by JavaScript -->
<div class="col-span-full text-center py-12 text-gray-500 hidden" id="noBooksMessage">
<div class="text-6xl mb-4">📖</div>
<p class="text-xl">Nenhum livro cadastrado ainda</p>
<p class="mt-2">Adicione seu primeiro livro usando o formulário acima</p>
</div>
</div>
</section>
<!-- Import/Export Section -->
<section class="bg-white rounded-xl shadow-sm p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-6 pb-2 border-b">Importar/Exportar Livros</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="border border-gray-200 rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-800 mb-4">📥 Importar Livros</h3>
<p class="text-gray-600 mb-4">Importe livros a partir de um arquivo CSV. Campos não preenchidos serão ignorados.</p>
<input type="file" id="csvFile" accept=".csv" class="w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-lg file:border-0
file:text-sm file:font-semibold
file:bg-primary file:text-white
hover:file:bg-indigo-700
cursor-pointer">
<button id="importBtn" class="mt-4 w-full px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium rounded-lg transition duration-200">
Importar CSV
</button>
</div>
<div class="border border-gray-200 rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-800 mb-4">📤 Exportar Livros</h3>
<p class="text-gray-600 mb-4">Exporte todos os livros cadastrados para um arquivo Excel (.xlsx).</p>
<button id="exportBtn" class="w-full px-4 py-2 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg transition duration-200">
Exportar para Excel
</button>
</div>
</div>
<div class="mt-4 text-center text-sm text-gray-500">
developed by Vitão
</div>
</section>
</div>
<script>
// Sample books data
let books = JSON.parse(localStorage.getItem('books')) || [];
// DOM Elements
const bookForm = document.getElementById('bookForm');
const searchForm = document.getElementById('searchForm');
const booksList = document.getElementById('booksList');
const noBooksMessage = document.getElementById('noBooksMessage');
const clearSearchBtn = document.getElementById('clearSearch');
const toggleFormBtn = document.getElementById('toggleForm');
const formIcon = document.getElementById('formIcon');
// Render books
function renderBooks(filteredBooks = null) {
const booksToRender = filteredBooks || books;
if (booksToRender.length === 0) {
noBooksMessage.classList.remove('hidden');
booksList.innerHTML = '';
booksList.appendChild(noBooksMessage);
return;
}
noBooksMessage.classList.add('hidden');
booksList.innerHTML = '';
booksToRender.forEach((book, index) => {
const bookCard = document.createElement('div');
bookCard.className = 'book-card bg-white rounded-xl shadow-sm overflow-hidden transition-all duration-300';
bookCard.innerHTML = `
<div class="p-6">
<div class="flex justify-between items-start">
<div>
<h3 class="text-xl font-bold text-gray-800 mb-1">${book.title}</h3>
<p class="text-gray-600 mb-3">por ${book.author}</p>
</div>
<div class="flex items-center gap-2">
<span class="bg-indigo-100 text-indigo-800 text-xs font-semibold px-2.5 py-1 rounded-full">
${book.cabinet}
</span>
<span class="bg-green-100 text-green-800 text-xs font-semibold px-2.5 py-1 rounded-full">
Sala ${book.room}
</span>
</div>
</div>
<div class="mt-4 space-y-2">
<div class="flex items-center text-gray-600 mb-1">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
<span class="font-semibold">Sala ${book.room}</span>
</div>
<div class="flex items-center text-gray-600 mb-2">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<span class="font-semibold">${book.cabinet}</span>
</div>
<div class="flex items-center text-gray-600">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<span>${book.publisher}</span>
</div>
<div class="flex items-center text-gray-600">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<span>${book.year}</span>
</div>
</div>
<div class="mt-4 pt-4 border-t border-gray-100 flex justify-end space-x-2">
<button class="edit-book px-3 py-1 text-sm bg-yellow-100 text-yellow-800 rounded hover:bg-yellow-200 transition" data-index="${index}">
Editar
</button>
<button class="delete-book px-3 py-1 text-sm bg-red-100 text-red-800 rounded hover:bg-red-200 transition" data-index="${index}">
Excluir
</button>
</div>
</div>
`;
booksList.appendChild(bookCard);
});
}
// Edit book
function setupEditBook(index) {
const book = books[index];
document.getElementById('title').value = book.title;
document.getElementById('author').value = book.author;
document.getElementById('publisher').value = book.publisher;
document.getElementById('year').value = book.year;
document.getElementById('room').value = book.room;
document.getElementById('cabinet').value = book.cabinet;
// Change form button to "Atualizar"
const submitBtn = bookForm.querySelector('button[type="submit"]');
submitBtn.textContent = '📖 Atualizar Livro';
submitBtn.dataset.editIndex = index;
// Show form if hidden
bookForm.classList.remove('hidden');
formIcon.classList.remove('rotate-180');
// Scroll to form
bookForm.scrollIntoView({ behavior: 'smooth' });
}
// Delete book
function deleteBook(index) {
if (confirm('Tem certeza que deseja excluir este livro?')) {
books.splice(index, 1);
localStorage.setItem('books', JSON.stringify(books));
renderBooks();
}
}
// Handle book form submission
bookForm.addEventListener('submit', function(e) {
e.preventDefault();
const editIndex = e.target.querySelector('button[type="submit"]').dataset.editIndex;
const bookData = {
title: document.getElementById('title').value,
author: document.getElementById('author').value,
publisher: document.getElementById('publisher').value,
year: document.getElementById('year').value,
room: document.getElementById('room').value,
cabinet: document.getElementById('cabinet').value
};
if (editIndex !== undefined) {
// Update existing book
books[editIndex] = bookData;
// Reset form button
const submitBtn = bookForm.querySelector('button[type="submit"]');
submitBtn.textContent = '📖 Salvar Livro';
delete submitBtn.dataset.editIndex;
} else {
// Add new book
books.push(bookData);
}
localStorage.setItem('books', JSON.stringify(books));
// Reset form
bookForm.reset();
// Re-render books
renderBooks();
// Scroll to books section
document.querySelector('section:last-child').scrollIntoView({ behavior: 'smooth' });
});
// Handle search form submission
searchForm.addEventListener('submit', function(e) {
e.preventDefault();
const searchTitle = document.getElementById('searchTitle').value.toLowerCase();
const searchAuthor = document.getElementById('searchAuthor').value.toLowerCase();
const searchPublisher = document.getElementById('searchPublisher').value.toLowerCase();
const searchYear = document.getElementById('searchYear').value;
const searchRoom = document.getElementById('searchRoom').value.toLowerCase();
const searchCabinet = document.getElementById('searchCabinet').value;
const filteredBooks = books.filter(book => {
return (
(searchTitle === '' || book.title.toLowerCase().includes(searchTitle)) &&
(searchAuthor === '' || book.author.toLowerCase().includes(searchAuthor)) &&
(searchPublisher === '' || book.publisher.toLowerCase().includes(searchPublisher)) &&
(searchYear === '' || book.year.includes(searchYear)) &&
(searchRoom === '' || book.room.toLowerCase().includes(searchRoom)) &&
(searchCabinet === '' || book.cabinet === searchCabinet)
);
});
renderBooks(filteredBooks);
});
// Clear search form
clearSearchBtn.addEventListener('click', function() {
searchForm.reset();
renderBooks();
});
// Toggle form visibility
toggleFormBtn.addEventListener('click', function() {
bookForm.classList.toggle('hidden');
formIcon.classList.toggle('rotate-180');
});
// Event delegation for edit/delete buttons
document.addEventListener('click', function(e) {
if (e.target.classList.contains('edit-book')) {
setupEditBook(e.target.dataset.index);
}
if (e.target.classList.contains('delete-book')) {
deleteBook(e.target.dataset.index);
}
});
// Import books from CSV
document.getElementById('importBtn').addEventListener('click', function() {
const fileInput = document.getElementById('csvFile');
const file = fileInput.files[0];
if (!file) {
alert('Por favor, selecione um arquivo CSV para importar.');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const contents = e.target.result;
const lines = contents.split('\n');
const headers = lines[0].split(',');
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === '') continue;
const values = lines[i].split(',');
const book = {};
for (let j = 0; j < headers.length; j++) {
const header = headers[j].trim().toLowerCase();
const value = values[j] ? values[j].trim() : '';
if (header === 'título do livro' || header === 'titulo do livro') book.title = value;
else if (header === 'autor') book.author = value;
else if (header === 'editora') book.publisher = value;
else if (header === 'ano') book.year = value;
else if (header === 'sala') book.room = value;
else if (header === 'armário' || header === 'armario') book.cabinet = value;
}
if (book.title) {
books.push(book);
}
}
localStorage.setItem('books', JSON.stringify(books));
renderBooks();
alert(`Importados ${lines.length - 1} livros com sucesso!`);
fileInput.value = '';
};
reader.readAsText(file);
});
// Export books to Excel
document.getElementById('exportBtn').addEventListener('click', function() {
if (books.length === 0) {
alert('Não há livros para exportar.');
return;
}
// Create CSV content with semicolon delimiter
let csvContent = "Titulo do Livro;Autor;Editora;Ano;Sala;Armario\n";
books.forEach(book => {
csvContent += `${book.title};${book.author};${book.publisher};${book.year};${book.room};${book.cabinet}\n`;
});
// Create download link
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
const blobWithBOM = new Blob([bom, csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', 'livros_estante_digital.csv');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
// Initial render
renderBooks();
</script>
</body>
</html>