Asistent AI
La zi
{
"components": [
{
"slug": "navbar",
"component_name": "Navigation Bar",
"component_description": "Navbar pentru navigarea între paginile aplicației",
"component_code": "<nav class=\"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-50\">\n <div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div class=\"flex items-center justify-between h-16\">\n <div class=\"flex items-center gap-2 cursor-pointer\" onclick=\"navigateTo('dashboard')\">\n <svg class=\"w-7 h-7 text-blue-600 dark:text-blue-400\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\"/></svg>\n <span class=\"text-lg font-bold text-gray-900 dark:text-gray-100\">Biblioteca</span>\n </div>\n <div class=\"hidden md:flex items-center gap-1\" id=\"nav-links-desktop\"></div>\n <button onclick=\"toggleMobileMenu()\" class=\"md:hidden p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer border-none bg-transparent\" id=\"mobile-menu-btn\">\n <svg class=\"w-6 h-6\" id=\"menu-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 6h16M4 12h16M4 18h16\"/></svg>\n <svg class=\"w-6 h-6 hidden\" id=\"close-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"/></svg>\n </button>\n </div>\n </div>\n <div class=\"md:hidden hidden border-t border-gray-200 dark:border-gray-700\" id=\"mobile-menu\">\n <div class=\"px-4 py-3 space-y-1\" id=\"nav-links-mobile\"></div>\n </div>\n</nav>",
"component_script": "function navigateTo(slug) {\n sdk.navigate(slug);\n}\n\nconst navLinks = [\n { slug: 'dashboard', label: 'Dashboard', icon: '<svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0a1 1 0 01-1-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 01-1 1\"/></svg>' },\n { slug: 'books', label: 'Cărți', icon: '<svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\"/></svg>' },\n { slug: 'add-book', label: 'Adaugă carte', icon: '<svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4v16m8-8H4\"/></svg>' }\n];\n\nfunction getCurrentPageSlug() {\n const path = window.location.pathname;\n const parts = path.split('/');\n return parts[parts.length - 1] || 'dashboard';\n}\n\nfunction renderNavLinks() {\n const currentSlug = getCurrentPageSlug();\n const desktopContainer = document.getElementById('nav-links-desktop');\n const mobileContainer = document.getElementById('nav-links-mobile');\n if (!desktopContainer || !mobileContainer) return;\n\n desktopContainer.innerHTML = navLinks.map(link => {\n const isActive = currentSlug === link.slug;\n const activeClasses = 'bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-semibold';\n const inactiveClasses = 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-200';\n return `<button onclick=\"navigateTo('${link.slug}')\" class=\"inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors cursor-pointer border-none bg-transparent ${isActive ? activeClasses : inactiveClasses}\">${link.icon}<span>${link.label}</span></button>`;\n }).join('');\n\n mobileContainer.innerHTML = navLinks.map(link => {\n const isActive = currentSlug === link.slug;\n const activeClasses = 'bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-semibold';\n const inactiveClasses = 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700';\n return `<button onclick=\"navigateTo('${link.slug}'); closeMobileMenu();\" class=\"w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors cursor-pointer border-none bg-transparent text-left ${isActive ? activeClasses : inactiveClasses}\">${link.icon}<span>${link.label}</span></button>`;\n }).join('');\n}\n\nfunction toggleMobileMenu() {\n const menu = document.getElementById('mobile-menu');\n const menuIcon = document.getElementById('menu-icon');\n const closeIcon = document.getElementById('close-icon');\n const isOpen = !menu.classList.contains('hidden');\n if (isOpen) {\n menu.classList.add('hidden');\n menuIcon.classList.remove('hidden');\n closeIcon.classList.add('hidden');\n } else {\n menu.classList.remove('hidden');\n menuIcon.classList.add('hidden');\n closeIcon.classList.remove('hidden');\n }\n}\n\nfunction closeMobileMenu() {\n const menu = document.getElementById('mobile-menu');\n const menuIcon = document.getElementById('menu-icon');\n const closeIcon = document.getElementById('close-icon');\n menu.classList.add('hidden');\n menuIcon.classList.remove('hidden');\n closeIcon.classList.add('hidden');\n}\n\nrenderNavLinks();"
},
{
"slug": "page-layout",
"component_name": "Page Layout",
"component_description": "Layout wrapper cu padding și max-width pentru conținutul paginilor",
"component_code": "<div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8 bg-gray-50 dark:bg-gray-900 min-h-[calc(100vh-4rem)]\">\n <div data-slot=\"content\"></div>\n</div>"
},
{
"slug": "stats-cards",
"component_name": "Stats Cards",
"component_description": "Carduri cu statistici generale despre lecturi",
"component_code": "<div class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-10\" id=\"stats-cards\"></div>",
"component_script": "async function loadStats() {\n try {\n const books = await sdk.getRecords('books');\n const totalBooks = books.length;\n const totalPages = books.reduce((sum, b) => sum + (b['page-count'] || 0), 0);\n const avgRating = totalBooks > 0 ? (books.reduce((sum, b) => sum + (b.rating || 0), 0) / totalBooks).toFixed(1) : '0.0';\n const thisYear = new Date().getFullYear();\n const booksThisYear = books.filter(b => b['date-read'] && new Date(b['date-read']).getFullYear() === thisYear).length;\n\n const stats = [\n { label: 'Cărți citite', value: totalBooks, icon: '<svg class=\"w-8 h-8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\"/></svg>', color: 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-800 text-blue-700 dark:text-blue-300' },\n { label: 'Pagini citite', value: totalPages, icon: '<svg class=\"w-8 h-8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z\"/></svg>', color: 'bg-emerald-50 dark:bg-emerald-900/30 border-emerald-200 dark:border-emerald-800 text-emerald-700 dark:text-emerald-300' },\n { label: 'Rating mediu', value: avgRating, icon: '<svg class=\"w-8 h-8\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\"/></svg>', color: 'bg-amber-50 dark:bg-amber-900/30 border-amber-200 dark:border-amber-800 text-amber-700 dark:text-amber-300' },\n { label: 'Cărți anul acesta', value: booksThisYear, icon: '<svg class=\"w-8 h-8\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" 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\"/></svg>', color: 'bg-rose-50 dark:bg-rose-900/30 border-rose-200 dark:border-rose-800 text-rose-700 dark:text-rose-300' }\n ];\n\n document.getElementById('stats-cards').innerHTML = stats.map(s => `\n <div class=\"rounded-xl border p-6 ${s.color} transition-shadow duration-200 hover:shadow-lg\">\n <div class=\"flex items-center justify-between gap-4\">\n <div class=\"min-w-0\">\n <p class=\"text-sm font-medium opacity-80 mb-2\">${s.label}</p>\n <p class=\"text-3xl font-bold tracking-tight\">${s.value}</p>\n </div>\n <span class=\"flex-shrink-0 opacity-70\">${s.icon}</span>\n </div>\n </div>\n `).join('');\n } catch (error) {\n console.error('Eroare la încărcarea statisticilor:', error);\n }\n}\n\nloadStats();\nsdk.on('books:changed', loadStats);"
},
{
"slug": "recent-books",
"component_name": "Recent Books",
"component_description": "Lista ultimelor cărți adăugate",
"component_code": "<div class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-8\">\n <div class=\"flex items-center justify-between mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 dark:text-gray-100\">Cărți recente</h2>\n <button onclick=\"navigateToBooks()\" class=\"text-sm text-blue-600 dark:text-blue-400 hover:underline cursor-pointer bg-transparent border-none p-0\">Vezi toate →</button>\n </div>\n <div id=\"recent-books-list\" class=\"space-y-4\">\n <p class=\"text-gray-500 dark:text-gray-400 text-sm py-4\">Se încarcă...</p>\n </div>\n</div>",
"component_script": "function navigateToBooks() {\n sdk.navigate('books');\n}\n\nasync function loadRecentBooks() {\n try {\n const books = await sdk.getRecords('books', {}, { sort: '-createdAt', limit: 5 });\n const container = document.getElementById('recent-books-list');\n if (books.length === 0) {\n container.innerHTML = '<p class=\"text-gray-500 dark:text-gray-400 text-sm py-4\">Nu ai adăugat nicio carte încă.</p>';\n return;\n }\n container.innerHTML = books.map(b => {\n const stars = '⭐'.repeat(b.rating || 0);\n return `\n <div class=\"flex items-center justify-between p-4 rounded-lg bg-gray-50 dark:bg-gray-700/50 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer\" onclick=\"viewRecentBook('${b.id}')\">\n <div class=\"flex-1 min-w-0 pr-4\">\n <p class=\"font-medium text-gray-900 dark:text-gray-100 truncate mb-1\">${b.title || 'Fără titlu'}</p>\n <p class=\"text-sm text-gray-500 dark:text-gray-400 truncate\">${b.author || 'Autor necunoscut'}</p>\n </div>\n <div class=\"ml-4 text-sm flex-shrink-0\">${stars || '<span class=\\'text-gray-400 dark:text-gray-500\\'>–</span>'}</div>\n </div>\n `;\n }).join('');\n } catch (error) {\n console.error('Eroare la încărcarea cărților recente:', error);\n }\n}\n\nfunction viewRecentBook(id) {\n sdk.navigate('book-detail', { recordId: id });\n}\n\nloadRecentBooks();\nsdk.on('books:changed', loadRecentBooks);"
},
{
"slug": "genre-chart",
"component_name": "Genre Chart",
"component_description": "Distribuția cărților pe genuri",
"component_code": "<div class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-8\">\n <h2 class=\"text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6\">Distribuție pe genuri</h2>\n <div id=\"genre-chart\" class=\"space-y-5\">\n <p class=\"text-gray-500 dark:text-gray-400 text-sm py-4\">Se încarcă...</p>\n </div>\n</div>",
"component_script": "async function loadGenreChart() {\n try {\n const books = await sdk.getRecords('books');\n const container = document.getElementById('genre-chart');\n if (books.length === 0) {\n container.innerHTML = '<p class=\"text-gray-500 dark:text-gray-400 text-sm py-4\">Nu ai adăugat nicio carte încă.</p>';\n return;\n }\n const genreCount = {};\n books.forEach(b => {\n const genre = b.genre || 'Neclasificat';\n genreCount[genre] = (genreCount[genre] || 0) + 1;\n });\n const sorted = Object.entries(genreCount).sort((a, b) => b[1] - a[1]);\n const max = sorted[0][1];\n const colors = ['bg-blue-500', 'bg-emerald-500', 'bg-amber-500', 'bg-rose-500', 'bg-purple-500', 'bg-cyan-500', 'bg-orange-500', 'bg-teal-500'];\n container.innerHTML = sorted.map(([genre, count], i) => {\n const pct = Math.round((count / max) * 100);\n const color = colors[i % colors.length];\n return `\n <div>\n <div class=\"flex justify-between text-sm mb-2\">\n <span class=\"text-gray-700 dark:text-gray-300 font-medium\">${genre}</span>\n <span class=\"text-gray-500 dark:text-gray-400 ml-4\">${count} ${count === 1 ? 'carte' : 'cărți'}</span>\n </div>\n <div class=\"w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3\">\n <div class=\"${color} h-3 rounded-full transition-all duration-500\" style=\"width: ${pct}%\"></div>\n </div>\n </div>\n `;\n }).join('');\n } catch (error) {\n console.error('Eroare la încărcarea chart-ului:', error);\n }\n}\n\nloadGenreChart();\nsdk.on('books:changed', loadGenreChart);"
},
{
"slug": "books-content",
"component_name": "Books Page Layout",
"component_description": "Layout complet cu titlu, panou lateral de filtre și grid de cărți",
"component_code": "<div>\n <!-- Page Header -->\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100\">Cărți</h1>\n <button onclick=\"goToAddBook()\" class=\"inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium transition-colors whitespace-nowrap cursor-pointer border-none\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4v16m8-8H4\"/></svg>\n Adaugă carte\n </button>\n </div>\n\n <!-- Mobile Filter Toggle -->\n <div class=\"lg:hidden mb-4\">\n <button onclick=\"toggleFilters()\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 flex items-center justify-between cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors\">\n <span class=\"flex items-center gap-2\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z\"/></svg>\n Filtre\n <span id=\"active-filters-badge\" class=\"hidden ml-1 px-1.5 py-0.5 text-xs font-bold rounded-full bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300\"></span>\n </span>\n <svg id=\"filter-chevron\" class=\"w-4 h-4 text-gray-400 transition-transform duration-200\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\"/></svg>\n </button>\n </div>\n\n <!-- Main Layout -->\n <div class=\"flex flex-col lg:flex-row gap-6\">\n <!-- Sidebar -->\n <aside id=\"filters-sidebar\" class=\"hidden lg:block w-full lg:w-72 lg:flex-shrink-0\">\n <div class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-5 lg:sticky lg:top-6\">\n <div class=\"flex items-center justify-between mb-5\">\n <h3 class=\"font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z\"/></svg>\n Filtre\n </h3>\n <button onclick=\"resetFilters()\" id=\"reset-btn\" class=\"text-xs text-blue-600 dark:text-blue-400 hover:underline cursor-pointer bg-transparent border-none p-0 hidden\">Resetează</button>\n </div>\n\n <!-- Search -->\n <div class=\"mb-5\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Căutare</label>\n <div class=\"relative\">\n <svg class=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"/></svg>\n <input type=\"text\" id=\"search-input\" placeholder=\"Titlu sau autor...\" class=\"w-full pl-10 pr-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\" oninput=\"filterBooks()\">\n </div>\n </div>\n\n <!-- Genre Filter -->\n <div class=\"mb-5\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Gen</label>\n <select id=\"genre-filter\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-blue-500\" onchange=\"filterBooks()\">\n <option value=\"\">Toate genurile</option>\n </select>\n </div>\n\n <!-- Rating Filter -->\n <div class=\"mb-5\">\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Rating minim</label>\n <select id=\"rating-filter\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-blue-500\" onchange=\"filterBooks()\">\n <option value=\"\">Orice rating</option>\n <option value=\"5\">⭐⭐⭐⭐⭐</option>\n <option value=\"4\">⭐⭐⭐⭐+</option>\n <option value=\"3\">⭐⭐⭐+</option>\n <option value=\"2\">⭐⭐+</option>\n <option value=\"1\">⭐+</option>\n </select>\n </div>\n\n <!-- Active filters info -->\n <div id=\"filter-info\" class=\"hidden pt-4 border-t border-gray-100 dark:border-gray-700\">\n <p class=\"text-xs text-gray-500 dark:text-gray-400\" id=\"filter-info-text\"></p>\n </div>\n </div>\n </aside>\n\n <!-- Main Content -->\n <main class=\"flex-1 min-w-0\">\n <div class=\"flex items-center justify-between mb-4\">\n <p id=\"books-count\" class=\"text-sm text-gray-500 dark:text-gray-400\"></p>\n </div>\n <div id=\"books-grid\" class=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-5\">\n <div class=\"col-span-full flex flex-col items-center justify-center py-16\">\n <svg class=\"w-12 h-12 text-gray-300 dark:text-gray-600 mb-3 animate-pulse\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\"/></svg>\n <p class=\"text-gray-400 dark:text-gray-500 text-sm\">Se încarcă cărțile...</p>\n </div>\n </div>\n </main>\n </div>\n</div>",
"component_script": "let allBooks = [];\n\nconst genreColors = {\n 'Ficțiune': { bg: 'bg-violet-100 dark:bg-violet-900/40', text: 'text-violet-700 dark:text-violet-300', border: 'border-l-violet-500 dark:border-l-violet-400', accent: 'bg-violet-500' },\n 'Non-ficțiune': { bg: 'bg-cyan-100 dark:bg-cyan-900/40', text: 'text-cyan-700 dark:text-cyan-300', border: 'border-l-cyan-500 dark:border-l-cyan-400', accent: 'bg-cyan-500' },\n 'Science Fiction': { bg: 'bg-indigo-100 dark:bg-indigo-900/40', text: 'text-indigo-700 dark:text-indigo-300', border: 'border-l-indigo-500 dark:border-l-indigo-400', accent: 'bg-indigo-500' },\n 'Fantasy': { bg: 'bg-purple-100 dark:bg-purple-900/40', text: 'text-purple-700 dark:text-purple-300', border: 'border-l-purple-500 dark:border-l-purple-400', accent: 'bg-purple-500' },\n 'Thriller': { bg: 'bg-red-100 dark:bg-red-900/40', text: 'text-red-700 dark:text-red-300', border: 'border-l-red-500 dark:border-l-red-400', accent: 'bg-red-500' },\n 'Mister': { bg: 'bg-slate-100 dark:bg-slate-900/40', text: 'text-slate-700 dark:text-slate-300', border: 'border-l-slate-500 dark:border-l-slate-400', accent: 'bg-slate-500' },\n 'Romance': { bg: 'bg-pink-100 dark:bg-pink-900/40', text: 'text-pink-700 dark:text-pink-300', border: 'border-l-pink-500 dark:border-l-pink-400', accent: 'bg-pink-500' },\n 'Istorie': { bg: 'bg-amber-100 dark:bg-amber-900/40', text: 'text-amber-700 dark:text-amber-300', border: 'border-l-amber-500 dark:border-l-amber-400', accent: 'bg-amber-500' },\n 'Biografie': { bg: 'bg-teal-100 dark:bg-teal-900/40', text: 'text-teal-700 dark:text-teal-300', border: 'border-l-teal-500 dark:border-l-teal-400', accent: 'bg-teal-500' },\n 'Dezvoltare personală': { bg: 'bg-emerald-100 dark:bg-emerald-900/40', text: 'text-emerald-700 dark:text-emerald-300', border: 'border-l-emerald-500 dark:border-l-emerald-400', accent: 'bg-emerald-500' },\n 'Filozofie': { bg: 'bg-gray-100 dark:bg-gray-900/40', text: 'text-gray-700 dark:text-gray-300', border: 'border-l-gray-500 dark:border-l-gray-400', accent: 'bg-gray-500' },\n 'Poezie': { bg: 'bg-fuchsia-100 dark:bg-fuchsia-900/40', text: 'text-fuchsia-700 dark:text-fuchsia-300', border: 'border-l-fuchsia-500 dark:border-l-fuchsia-400', accent: 'bg-fuchsia-500' },\n 'Clasic': { bg: 'bg-yellow-100 dark:bg-yellow-900/40', text: 'text-yellow-700 dark:text-yellow-300', border: 'border-l-yellow-500 dark:border-l-yellow-400', accent: 'bg-yellow-500' }\n};\nconst defaultGenreColor = { bg: 'bg-blue-100 dark:bg-blue-900/40', text: 'text-blue-700 dark:text-blue-300', border: 'border-l-blue-500 dark:border-l-blue-400', accent: 'bg-blue-500' };\n\nfunction getGenreColor(genre) {\n return genreColors[genre] || defaultGenreColor;\n}\n\nfunction goToAddBook() {\n sdk.navigate('add-book');\n}\n\nfunction viewBook(id) {\n sdk.navigate('book-detail', { recordId: id });\n}\n\nfunction editBook(id) {\n sdk.navigate('edit-book', { recordId: id });\n}\n\nfunction toggleFilters() {\n const sidebar = document.getElementById('filters-sidebar');\n const chevron = document.getElementById('filter-chevron');\n sidebar.classList.toggle('hidden');\n if (!sidebar.classList.contains('hidden')) {\n chevron.style.transform = 'rotate(180deg)';\n } else {\n chevron.style.transform = '';\n }\n}\n\nfunction updateFilterUI() {\n const search = document.getElementById('search-input').value;\n const genre = document.getElementById('genre-filter').value;\n const rating = document.getElementById('rating-filter').value;\n const resetBtn = document.getElementById('reset-btn');\n const badge = document.getElementById('active-filters-badge');\n const filterInfo = document.getElementById('filter-info');\n const filterInfoText = document.getElementById('filter-info-text');\n\n let activeCount = 0;\n if (search) activeCount++;\n if (genre) activeCount++;\n if (rating) activeCount++;\n\n if (activeCount > 0) {\n resetBtn.classList.remove('hidden');\n badge.classList.remove('hidden');\n badge.textContent = activeCount;\n filterInfo.classList.remove('hidden');\n const parts = [];\n if (search) parts.push('„' + search + '\"');\n if (genre) parts.push(genre);\n if (rating) parts.push('rating ≥ ' + rating);\n filterInfoText.textContent = 'Filtre active: ' + parts.join(', ');\n } else {\n resetBtn.classList.add('hidden');\n badge.classList.add('hidden');\n filterInfo.classList.add('hidden');\n }\n}\n\nfunction resetFilters() {\n document.getElementById('search-input').value = '';\n document.getElementById('genre-filter').value = '';\n document.getElementById('rating-filter').value = '';\n filterBooks();\n}\n\nasync function loadGenres() {\n try {\n const books = await sdk.getRecords('books');\n const genres = [...new Set(books.map(b => b.genre).filter(Boolean))].sort();\n const select = document.getElementById('genre-filter');\n select.innerHTML = '<option value=\"\">Toate genurile</option>' + genres.map(g => `<option value=\"${g}\">${g}</option>`).join('');\n } catch (error) {\n console.error(error);\n }\n}\n\nasync function loadBooks() {\n try {\n allBooks = await sdk.getRecords('books');\n loadGenres();\n renderBooks(allBooks);\n updateCount(allBooks.length, allBooks.length);\n } catch (error) {\n console.error('Eroare la încărcarea cărților:', error);\n }\n}\n\nfunction updateCount(filtered, total) {\n const el = document.getElementById('books-count');\n if (filtered === total) {\n el.textContent = total + (total === 1 ? ' carte' : ' cărți');\n } else {\n el.textContent = filtered + ' din ' + total + (total === 1 ? ' carte' : ' cărți');\n }\n}\n\nfunction filterBooks() {\n const search = document.getElementById('search-input').value.toLowerCase();\n const genre = document.getElementById('genre-filter').value;\n const ratingMin = parseInt(document.getElementById('rating-filter').value) || 0;\n\n const filtered = allBooks.filter(b => {\n const matchSearch = !search || (b.title || '').toLowerCase().includes(search) || (b.author || '').toLowerCase().includes(search);\n const matchGenre = !genre || b.genre === genre;\n const matchRating = (b.rating || 0) >= ratingMin;\n return matchSearch && matchGenre && matchRating;\n });\n renderBooks(filtered);\n updateCount(filtered.length, allBooks.length);\n updateFilterUI();\n}\n\nfunction renderBooks(books) {\n const container = document.getElementById('books-grid');\n if (!container) return;\n if (books.length === 0) {\n container.innerHTML = '<div class=\"col-span-full flex flex-col items-center justify-center py-16\"><svg class=\"w-16 h-16 text-gray-300 dark:text-gray-600 mb-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\"/></svg><p class=\"text-gray-500 dark:text-gray-400 text-lg font-medium\">Nu s-au găsit cărți</p><p class=\"text-gray-400 dark:text-gray-500 text-sm mt-1\">Încearcă să modifici filtrele sau adaugă o carte nouă</p></div>';\n return;\n }\n container.innerHTML = books.map(b => {\n const gc = getGenreColor(b.genre);\n const starsFilled = b.rating ? '<span class=\"text-amber-400\">' + '★'.repeat(b.rating) + '</span>' : '';\n const starsEmpty = b.rating ? '<span class=\"text-gray-300 dark:text-gray-600\">' + '☆'.repeat(5 - b.rating) + '</span>' : '<span class=\"text-gray-300 dark:text-gray-600\">☆☆☆☆☆</span>';\n const dateStr = b['date-read'] ? new Date(b['date-read']).toLocaleDateString('ro-RO', { day: 'numeric', month: 'short', year: 'numeric' }) : '';\n const initials = (b.title || '?').substring(0, 2).toUpperCase();\n return `\n <div class=\"group relative bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 border-l-4 ${gc.border} overflow-hidden hover:shadow-xl dark:hover:shadow-gray-900/50 hover:-translate-y-1 transition-all duration-300\">\n <div class=\"p-5 pb-4\">\n <div class=\"flex items-start gap-4\">\n <div class=\"flex-shrink-0 w-12 h-16 rounded-lg ${gc.accent} flex items-center justify-center text-white font-bold text-sm shadow-md\">\n ${initials}\n </div>\n <div class=\"flex-1 min-w-0\">\n <h3 class=\"font-bold text-gray-900 dark:text-gray-100 text-base leading-tight mb-0.5 line-clamp-2\">${b.title || 'Fără titlu'}</h3>\n <p class=\"text-gray-500 dark:text-gray-400 text-sm\">de ${b.author || 'Autor necunoscut'}</p>\n </div>\n </div>\n </div>\n <div class=\"px-5 pb-3\">\n <div class=\"flex items-center gap-2 mb-3 flex-wrap\">\n <span class=\"inline-block px-2.5 py-0.5 rounded-full text-xs font-semibold ${gc.bg} ${gc.text}\">${b.genre || 'Neclasificat'}</span>\n <span class=\"text-sm tracking-wide\">${starsFilled}${starsEmpty}</span>\n </div>\n ${b.notes ? `<p class=\"text-gray-500 dark:text-gray-400 text-sm line-clamp-2 leading-relaxed mb-3\">${b.notes}</p>` : ''}\n </div>\n <div class=\"px-5 py-3 bg-gray-50 dark:bg-gray-750 border-t border-gray-100 dark:border-gray-700 flex items-center justify-between\">\n <div class=\"flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500\">\n ${b['page-count'] ? `<span class=\"flex items-center gap-1\"><svg class=\"w-3.5 h-3.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z\"/></svg>${b['page-count']} pag</span>` : ''}\n ${dateStr ? `<span class=\"flex items-center gap-1\"><svg class=\"w-3.5 h-3.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" 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\"/></svg>${dateStr}</span>` : ''}\n </div>\n <div class=\"flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity duration-200\">\n <button onclick=\"viewBook('${b.id}')\" class=\"p-1.5 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-400 hover:text-emerald-600 dark:hover:text-emerald-400 transition-colors\" title=\"Previzualizare\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\"/><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\"/></svg>\n </button>\n <button onclick=\"editBook('${b.id}')\" class=\"p-1.5 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors\" title=\"Editează\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"/></svg>\n </button>\n <button onclick=\"deleteBook('${b.id}')\" class=\"p-1.5 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors\" title=\"Șterge\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"/></svg>\n </button>\n </div>\n </div>\n </div>\n `;\n }).join('');\n}\n\nasync function deleteBook(id) {\n if (!confirm('Sigur vrei să ștergi această carte?')) return;\n try {\n await sdk.deleteRecord('books', id);\n sdk.emit('books:changed');\n loadBooks();\n } catch (error) {\n console.error('Eroare la ștergere:', error);\n }\n}\n\nloadBooks();\nsdk.on('books:changed', loadBooks);"
},
{
"slug": "add-book-form",
"component_name": "Add Book Form",
"component_description": "Formularul complet pentru adăugarea unei cărți noi",
"component_code": "<div class=\"max-w-2xl mx-auto\">\n <div class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 sm:p-8\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-2xl font-bold text-gray-900 dark:text-gray-100\">Adaugă o carte</h1>\n <button onclick=\"goToBooksList()\" class=\"text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors cursor-pointer bg-transparent border-none p-0\">← Înapoi la listă</button>\n </div>\n <form id=\"add-book-form\" onsubmit=\"handleAddBook(event)\" class=\"space-y-5\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Titlu *</label>\n <input type=\"text\" id=\"book-title\" required class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\" placeholder=\"Introdu titlul cărții\">\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Autor *</label>\n <input type=\"text\" id=\"book-author\" required class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\" placeholder=\"Introdu autorul\">\n </div>\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-5\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Gen</label>\n <select id=\"book-genre\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-blue-500\">\n <option value=\"\">Selectează genul</option>\n <option value=\"Ficțiune\">Ficțiune</option>\n <option value=\"Non-ficțiune\">Non-ficțiune</option>\n <option value=\"Science Fiction\">Science Fiction</option>\n <option value=\"Fantasy\">Fantasy</option>\n <option value=\"Thriller\">Thriller</option>\n <option value=\"Mister\">Mister</option>\n <option value=\"Romance\">Romance</option>\n <option value=\"Istorie\">Istorie</option>\n <option value=\"Biografie\">Biografie</option>\n <option value=\"Dezvoltare personală\">Dezvoltare personală</option>\n <option value=\"Filozofie\">Filozofie</option>\n <option value=\"Poezie\">Poezie</option>\n <option value=\"Clasic\">Clasic</option>\n <option value=\"Altele\">Altele</option>\n </select>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Număr pagini</label>\n <input type=\"number\" id=\"book-pages\" min=\"1\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\" placeholder=\"300\">\n </div>\n </div>\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-5\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Data citirii</label>\n <input type=\"date\" id=\"book-date\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\">\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Rating</label>\n <div id=\"star-rating\" class=\"flex gap-1 mt-2\">\n <button type=\"button\" onclick=\"setRating(1)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors star-btn\">★</button>\n <button type=\"button\" onclick=\"setRating(2)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors star-btn\">★</button>\n <button type=\"button\" onclick=\"setRating(3)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors star-btn\">★</button>\n <button type=\"button\" onclick=\"setRating(4)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors star-btn\">★</button>\n <button type=\"button\" onclick=\"setRating(5)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors star-btn\">★</button>\n </div>\n <input type=\"hidden\" id=\"book-rating\" value=\"0\">\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Notițe</label>\n <textarea id=\"book-notes\" rows=\"4\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none resize-y\" placeholder=\"Scrie o scurtă recenzie sau notițe despre carte...\"></textarea>\n </div>\n <div class=\"flex gap-3 pt-2\">\n <button type=\"submit\" id=\"submit-btn\" class=\"flex-1 px-6 py-3 rounded-lg bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-semibold transition-colors cursor-pointer\">Salvează cartea</button>\n <button type=\"button\" onclick=\"goToBooksList()\" class=\"px-6 py-3 rounded-lg border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 font-medium transition-colors cursor-pointer\">Anulează</button>\n </div>\n <div id=\"form-message\" class=\"hidden text-center text-sm py-2 rounded-lg\"></div>\n </form>\n </div>\n</div>",
"component_script": "let currentRating = 0;\n\nfunction goToBooksList() {\n sdk.navigate('books');\n}\n\nfunction setRating(value) {\n currentRating = value;\n document.getElementById('book-rating').value = value;\n const buttons = document.querySelectorAll('.star-btn');\n buttons.forEach((btn, i) => {\n if (i < value) {\n btn.classList.remove('text-gray-300', 'dark:text-gray-600');\n btn.classList.add('text-amber-400');\n } else {\n btn.classList.remove('text-amber-400');\n btn.classList.add('text-gray-300', 'dark:text-gray-600');\n }\n });\n}\n\nasync function handleAddBook(event) {\n event.preventDefault();\n const btn = document.getElementById('submit-btn');\n const msg = document.getElementById('form-message');\n\n const values = {\n title: document.getElementById('book-title').value.trim(),\n author: document.getElementById('book-author').value.trim(),\n genre: document.getElementById('book-genre').value,\n 'page-count': parseInt(document.getElementById('book-pages').value) || 0,\n 'date-read': document.getElementById('book-date').value || null,\n rating: parseInt(document.getElementById('book-rating').value) || 0,\n notes: document.getElementById('book-notes').value.trim()\n };\n\n btn.disabled = true;\n btn.textContent = 'Se salvează...';\n\n try {\n await sdk.createRecord('books', values);\n msg.className = 'text-center text-sm py-2 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300';\n msg.textContent = 'Cartea a fost adăugată cu succes!';\n msg.classList.remove('hidden');\n sdk.emit('books:changed');\n setTimeout(() => { sdk.navigate('books'); }, 1000);\n } catch (error) {\n msg.className = 'text-center text-sm py-2 rounded-lg bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300';\n msg.textContent = 'A apărut o eroare. Încearcă din nou.';\n msg.classList.remove('hidden');\n btn.disabled = false;\n btn.textContent = 'Salvează cartea';\n console.error(error);\n }\n}"
},
{
"slug": "edit-book-form",
"component_name": "Edit Book Form",
"component_description": "Formularul pentru editarea unei cărți existente cu date precompletate",
"component_code": "<div class=\"max-w-2xl mx-auto\">\n <div class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6 sm:p-8\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-2xl font-bold text-gray-900 dark:text-gray-100\">Editează cartea</h1>\n <button onclick=\"goBackToBooks()\" class=\"text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors cursor-pointer bg-transparent border-none p-0\">← Înapoi la listă</button>\n </div>\n <form id=\"edit-book-form\" onsubmit=\"handleEditBook(event)\" class=\"space-y-5\">\n <input type=\"hidden\" id=\"edit-book-id\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Titlu *</label>\n <input type=\"text\" id=\"edit-title\" required class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\" placeholder=\"Introdu titlul cărții\">\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Autor *</label>\n <input type=\"text\" id=\"edit-author\" required class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\" placeholder=\"Introdu autorul\">\n </div>\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-5\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Gen</label>\n <select id=\"edit-genre\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-blue-500\">\n <option value=\"\">Selectează genul</option>\n <option value=\"Ficțiune\">Ficțiune</option>\n <option value=\"Non-ficțiune\">Non-ficțiune</option>\n <option value=\"Science Fiction\">Science Fiction</option>\n <option value=\"Fantasy\">Fantasy</option>\n <option value=\"Thriller\">Thriller</option>\n <option value=\"Mister\">Mister</option>\n <option value=\"Romance\">Romance</option>\n <option value=\"Istorie\">Istorie</option>\n <option value=\"Biografie\">Biografie</option>\n <option value=\"Dezvoltare personală\">Dezvoltare personală</option>\n <option value=\"Filozofie\">Filozofie</option>\n <option value=\"Poezie\">Poezie</option>\n <option value=\"Clasic\">Clasic</option>\n <option value=\"Altele\">Altele</option>\n </select>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Număr pagini</label>\n <input type=\"number\" id=\"edit-pages\" min=\"1\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\" placeholder=\"300\">\n </div>\n </div>\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-5\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Data citirii</label>\n <input type=\"date\" id=\"edit-date\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none\">\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Rating</label>\n <div id=\"edit-star-rating\" class=\"flex gap-1 mt-2\">\n <button type=\"button\" onclick=\"setEditRating(1)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors edit-star-btn\">★</button>\n <button type=\"button\" onclick=\"setEditRating(2)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors edit-star-btn\">★</button>\n <button type=\"button\" onclick=\"setEditRating(3)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors edit-star-btn\">★</button>\n <button type=\"button\" onclick=\"setEditRating(4)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors edit-star-btn\">★</button>\n <button type=\"button\" onclick=\"setEditRating(5)\" class=\"text-3xl text-gray-300 dark:text-gray-600 hover:text-amber-400 dark:hover:text-amber-400 transition-colors edit-star-btn\">★</button>\n </div>\n <input type=\"hidden\" id=\"edit-rating\" value=\"0\">\n </div>\n </div>\n <div>\n <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5\">Notițe</label>\n <textarea id=\"edit-notes\" rows=\"4\" class=\"w-full px-4 py-2.5 rounded-lg bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none resize-y\" placeholder=\"Scrie o scurtă recenzie sau notițe despre carte...\"></textarea>\n </div>\n <div class=\"flex gap-3 pt-2\">\n <button type=\"submit\" id=\"edit-submit-btn\" class=\"flex-1 px-6 py-3 rounded-lg bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-semibold transition-colors cursor-pointer\">Salvează modificările</button>\n <button type=\"button\" onclick=\"goBackToBooks()\" class=\"px-6 py-3 rounded-lg border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 font-medium transition-colors cursor-pointer\">Anulează</button>\n </div>\n <div id=\"edit-form-message\" class=\"hidden text-center text-sm py-2 rounded-lg\"></div>\n </form>\n </div>\n</div>",
"component_script": "let editRating = 0;\n\nfunction goBackToBooks() {\n sdk.navigate('books');\n}\n\nfunction setEditRating(value) {\n editRating = value;\n document.getElementById('edit-rating').value = value;\n const buttons = document.querySelectorAll('.edit-star-btn');\n buttons.forEach((btn, i) => {\n if (i < value) {\n btn.classList.remove('text-gray-300', 'dark:text-gray-600');\n btn.classList.add('text-amber-400');\n } else {\n btn.classList.remove('text-amber-400');\n btn.classList.add('text-gray-300', 'dark:text-gray-600');\n }\n });\n}\n\nasync function loadBookForEdit() {\n const bookId = sdk.getRecordIdFromUrl();\n if (!bookId) {\n sdk.navigate('books');\n return;\n }\n try {\n const book = await sdk.getRecord('books', bookId);\n document.getElementById('edit-book-id').value = book.id;\n document.getElementById('edit-title').value = book.title || '';\n document.getElementById('edit-author').value = book.author || '';\n document.getElementById('edit-genre').value = book.genre || '';\n document.getElementById('edit-pages').value = book['page-count'] || '';\n document.getElementById('edit-date').value = book['date-read'] ? book['date-read'].split('T')[0] : '';\n document.getElementById('edit-notes').value = book.notes || '';\n if (book.rating) {\n setEditRating(book.rating);\n }\n } catch (error) {\n console.error('Eroare la încărcarea cărții:', error);\n sdk.navigate('books');\n }\n}\n\nasync function handleEditBook(event) {\n event.preventDefault();\n const btn = document.getElementById('edit-submit-btn');\n const msg = document.getElementById('edit-form-message');\n const bookId = document.getElementById('edit-book-id').value;\n\n const values = {\n title: document.getElementById('edit-title').value.trim(),\n author: document.getElementById('edit-author').value.trim(),\n genre: document.getElementById('edit-genre').value,\n 'page-count': parseInt(document.getElementById('edit-pages').value) || 0,\n 'date-read': document.getElementById('edit-date').value || null,\n rating: parseInt(document.getElementById('edit-rating').value) || 0,\n notes: document.getElementById('edit-notes').value.trim()\n };\n\n btn.disabled = true;\n btn.textContent = 'Se salvează...';\n\n try {\n await sdk.updateRecord('books', bookId, values);\n msg.className = 'text-center text-sm py-2 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300';\n msg.textContent = 'Cartea a fost actualizată cu succes!';\n msg.classList.remove('hidden');\n sdk.emit('books:changed');\n setTimeout(() => { sdk.navigate('books'); }, 1000);\n } catch (error) {\n msg.className = 'text-center text-sm py-2 rounded-lg bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300';\n msg.textContent = 'A apărut o eroare. Încearcă din nou.';\n msg.classList.remove('hidden');\n btn.disabled = false;\n btn.textContent = 'Salvează modificările';\n console.error(error);\n }\n}\n\nloadBookForEdit();"
},
{
"slug": "book-detail-view",
"component_name": "Book Detail View",
"component_description": "Vizualizarea completă a detaliilor unei cărți cu acțiuni",
"component_code": "<div id=\"book-detail-container\" class=\"max-w-3xl mx-auto\">\n <div class=\"flex items-center justify-center py-20\">\n <svg class=\"w-10 h-10 text-gray-300 dark:text-gray-600 animate-pulse\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253\"/></svg>\n </div>\n</div>",
"component_script": "const detailGenreColors = {\n 'Ficțiune': { bg: 'bg-violet-100 dark:bg-violet-900/40', text: 'text-violet-700 dark:text-violet-300', accent: 'bg-violet-500' },\n 'Non-ficțiune': { bg: 'bg-cyan-100 dark:bg-cyan-900/40', text: 'text-cyan-700 dark:text-cyan-300', accent: 'bg-cyan-500' },\n 'Science Fiction': { bg: 'bg-indigo-100 dark:bg-indigo-900/40', text: 'text-indigo-700 dark:text-indigo-300', accent: 'bg-indigo-500' },\n 'Fantasy': { bg: 'bg-purple-100 dark:bg-purple-900/40', text: 'text-purple-700 dark:text-purple-300', accent: 'bg-purple-500' },\n 'Thriller': { bg: 'bg-red-100 dark:bg-red-900/40', text: 'text-red-700 dark:text-red-300', accent: 'bg-red-500' },\n 'Mister': { bg: 'bg-slate-100 dark:bg-slate-900/40', text: 'text-slate-700 dark:text-slate-300', accent: 'bg-slate-500' },\n 'Romance': { bg: 'bg-pink-100 dark:bg-pink-900/40', text: 'text-pink-700 dark:text-pink-300', accent: 'bg-pink-500' },\n 'Istorie': { bg: 'bg-amber-100 dark:bg-amber-900/40', text: 'text-amber-700 dark:text-amber-300', accent: 'bg-amber-500' },\n 'Biografie': { bg: 'bg-teal-100 dark:bg-teal-900/40', text: 'text-teal-700 dark:text-teal-300', accent: 'bg-teal-500' },\n 'Dezvoltare personală': { bg: 'bg-emerald-100 dark:bg-emerald-900/40', text: 'text-emerald-700 dark:text-emerald-300', accent: 'bg-emerald-500' },\n 'Filozofie': { bg: 'bg-gray-100 dark:bg-gray-900/40', text: 'text-gray-700 dark:text-gray-300', accent: 'bg-gray-500' },\n 'Poezie': { bg: 'bg-fuchsia-100 dark:bg-fuchsia-900/40', text: 'text-fuchsia-700 dark:text-fuchsia-300', accent: 'bg-fuchsia-500' },\n 'Clasic': { bg: 'bg-yellow-100 dark:bg-yellow-900/40', text: 'text-yellow-700 dark:text-yellow-300', accent: 'bg-yellow-500' }\n};\nconst detailDefaultColor = { bg: 'bg-blue-100 dark:bg-blue-900/40', text: 'text-blue-700 dark:text-blue-300', accent: 'bg-blue-500' };\n\nfunction getDetailGenreColor(genre) {\n return detailGenreColors[genre] || detailDefaultColor;\n}\n\nfunction goBackToBooks() {\n sdk.navigate('books');\n}\n\nfunction goToEditBook(id) {\n sdk.navigate('edit-book', { recordId: id });\n}\n\nasync function deleteBookFromDetail(id) {\n if (!confirm('Sigur vrei să ștergi această carte? Acțiunea este ireversibilă.')) return;\n try {\n await sdk.deleteRecord('books', id);\n sdk.emit('books:changed');\n sdk.navigate('books');\n } catch (error) {\n console.error('Eroare la ștergere:', error);\n }\n}\n\nasync function loadBookDetail() {\n const bookId = sdk.getRecordIdFromUrl();\n if (!bookId) {\n sdk.navigate('books');\n return;\n }\n\n try {\n const book = await sdk.getRecord('books', bookId);\n const container = document.getElementById('book-detail-container');\n const gc = getDetailGenreColor(book.genre);\n const initials = (book.title || '?').substring(0, 2).toUpperCase();\n\n const starsFilled = book.rating ? '<span class=\"text-amber-400 text-2xl\">' + '★'.repeat(book.rating) + '</span>' : '';\n const starsEmpty = book.rating ? '<span class=\"text-gray-300 dark:text-gray-600 text-2xl\">' + '☆'.repeat(5 - book.rating) + '</span>' : '<span class=\"text-gray-300 dark:text-gray-600 text-2xl\">☆☆☆☆☆</span>';\n\n const dateStr = book['date-read'] ? new Date(book['date-read']).toLocaleDateString('ro-RO', { day: 'numeric', month: 'long', year: 'numeric' }) : '';\n const createdStr = book.createdAt ? new Date(book.createdAt).toLocaleDateString('ro-RO', { day: 'numeric', month: 'long', year: 'numeric' }) : '';\n const editedStr = book.editedAt ? new Date(book.editedAt).toLocaleDateString('ro-RO', { day: 'numeric', month: 'long', year: 'numeric' }) : '';\n\n container.innerHTML = `\n <div class=\"mb-6\">\n <button onclick=\"goBackToBooks()\" class=\"inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors cursor-pointer bg-transparent border-none p-0\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 19l-7-7 7-7\"/></svg>\n Înapoi la listă\n </button>\n </div>\n\n <div class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-2xl overflow-hidden shadow-sm\">\n <!-- Header -->\n <div class=\"p-6 sm:p-8 border-b border-gray-100 dark:border-gray-700\">\n <div class=\"flex items-start gap-5\">\n <div class=\"flex-shrink-0 w-20 h-28 rounded-xl ${gc.accent} flex items-center justify-center text-white font-bold text-xl shadow-lg\">\n ${initials}\n </div>\n <div class=\"flex-1 min-w-0\">\n <h1 class=\"text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100 leading-tight mb-2\">${book.title || 'Fără titlu'}</h1>\n <p class=\"text-lg text-gray-500 dark:text-gray-400 mb-3\">de ${book.author || 'Autor necunoscut'}</p>\n <div class=\"flex items-center gap-3 flex-wrap\">\n ${book.genre ? `<span class=\"inline-block px-3 py-1 rounded-full text-sm font-semibold ${gc.bg} ${gc.text}\">${book.genre}</span>` : ''}\n <span class=\"tracking-wide\">${starsFilled}${starsEmpty}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Details Grid -->\n <div class=\"p-6 sm:p-8 border-b border-gray-100 dark:border-gray-700\">\n <h2 class=\"text-sm font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider mb-4\">Detalii</h2>\n <div class=\"grid grid-cols-1 sm:grid-cols-3 gap-5\">\n <div class=\"flex items-center gap-3 p-4 rounded-xl bg-gray-50 dark:bg-gray-700/50\">\n <div class=\"flex-shrink-0 w-10 h-10 rounded-lg bg-blue-100 dark:bg-blue-900/40 flex items-center justify-center\">\n <svg class=\"w-5 h-5 text-blue-600 dark:text-blue-400\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z\"/></svg>\n </div>\n <div>\n <p class=\"text-xs text-gray-500 dark:text-gray-400\">Pagini</p>\n <p class=\"text-lg font-semibold text-gray-900 dark:text-gray-100\">${book['page-count'] ? book['page-count'] + ' pag' : '–'}</p>\n </div>\n </div>\n <div class=\"flex items-center gap-3 p-4 rounded-xl bg-gray-50 dark:bg-gray-700/50\">\n <div class=\"flex-shrink-0 w-10 h-10 rounded-lg bg-emerald-100 dark:bg-emerald-900/40 flex items-center justify-center\">\n <svg class=\"w-5 h-5 text-emerald-600 dark:text-emerald-400\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" 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\"/></svg>\n </div>\n <div>\n <p class=\"text-xs text-gray-500 dark:text-gray-400\">Data citirii</p>\n <p class=\"text-lg font-semibold text-gray-900 dark:text-gray-100\">${dateStr || '–'}</p>\n </div>\n </div>\n <div class=\"flex items-center gap-3 p-4 rounded-xl bg-gray-50 dark:bg-gray-700/50\">\n <div class=\"flex-shrink-0 w-10 h-10 rounded-lg bg-amber-100 dark:bg-amber-900/40 flex items-center justify-center\">\n <svg class=\"w-5 h-5 text-amber-600 dark:text-amber-400\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\"/></svg>\n </div>\n <div>\n <p class=\"text-xs text-gray-500 dark:text-gray-400\">Rating</p>\n <p class=\"text-lg font-semibold text-gray-900 dark:text-gray-100\">${book.rating ? book.rating + '/5' : '–'}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Notes -->\n ${book.notes ? `\n <div class=\"p-6 sm:p-8 border-b border-gray-100 dark:border-gray-700\">\n <h2 class=\"text-sm font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider mb-4\">Notițe</h2>\n <div class=\"p-5 rounded-xl bg-gray-50 dark:bg-gray-700/50 border border-gray-100 dark:border-gray-600\">\n <p class=\"text-gray-700 dark:text-gray-300 leading-relaxed whitespace-pre-wrap\">${book.notes}</p>\n </div>\n </div>\n ` : ''}\n\n <!-- Metadata -->\n <div class=\"p-6 sm:p-8 border-b border-gray-100 dark:border-gray-700\">\n <div class=\"flex items-center gap-6 text-xs text-gray-400 dark:text-gray-500\">\n ${createdStr ? `<span class=\"flex items-center gap-1.5\"><svg class=\"w-3.5 h-3.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>Adăugat: ${createdStr}</span>` : ''}\n ${editedStr ? `<span class=\"flex items-center gap-1.5\"><svg class=\"w-3.5 h-3.5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\"/></svg>Modificat: ${editedStr}</span>` : ''}\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"p-6 sm:p-8\">\n <div class=\"flex flex-col sm:flex-row gap-3\">\n <button onclick=\"goToEditBook('${book.id}')\" class=\"flex-1 inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-semibold transition-colors cursor-pointer border-none\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"/></svg>\n Editează cartea\n </button>\n <button onclick=\"deleteBookFromDetail('${book.id}')\" class=\"inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl border border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 font-semibold transition-colors cursor-pointer bg-transparent\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"/></svg>\n Șterge\n </button>\n </div>\n </div>\n </div>\n `;\n } catch (error) {\n console.error('Eroare la încărcarea detaliilor:', error);\n const container = document.getElementById('book-detail-container');\n container.innerHTML = `\n <div class=\"mb-6\">\n <button onclick=\"goBackToBooks()\" class=\"inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors cursor-pointer bg-transparent border-none p-0\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 19l-7-7 7-7\"/></svg>\n Înapoi la listă\n </button>\n </div>\n <div class=\"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-10 text-center\">\n <svg class=\"w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z\"/></svg>\n <p class=\"text-gray-500 dark:text-gray-400 text-lg font-medium mb-2\">Cartea nu a fost găsită</p>\n <p class=\"text-gray-400 dark:text-gray-500 text-sm\">Este posibil să fi fost ștearsă sau ID-ul este invalid.</p>\n </div>\n `;\n }\n}\n\nloadBookDetail();"
}
],
"pages": [
{
"slug": "dashboard",
"title": "Dashboard",
"description": "Pagina principală cu statistici despre cărțile citite",
"components": [
"navbar",
{
"componentRef": "page-layout",
"children": [
{
"slot": "content",
"componentRef": "stats-cards"
},
{
"slot": "content",
"componentRef": "recent-books"
},
{
"slot": "content",
"componentRef": "genre-chart"
}
]
}
]
},
{
"slug": "books",
"title": "Cărțile mele",
"description": "Lista completă a cărților citite cu filtrare și căutare",
"components": [
"navbar",
{
"componentRef": "page-layout",
"children": [
{
"slot": "content",
"componentRef": "books-content"
}
]
}
]
},
{
"slug": "add-book",
"title": "Adaugă carte",
"description": "Formular pentru adăugarea unei noi cărți",
"components": [
"navbar",
{
"componentRef": "page-layout",
"children": [
{
"slot": "content",
"componentRef": "add-book-form"
}
]
}
]
},
{
"slug": "edit-book",
"title": "Editează carte",
"description": "Formular pentru editarea unei cărți existente",
"components": [
"navbar",
{
"componentRef": "page-layout",
"children": [
{
"slot": "content",
"componentRef": "edit-book-form"
}
]
}
]
},
{
"slug": "book-detail",
"title": "Detalii carte",
"description": "Pagina de previzualizare completă a unei cărți",
"components": [
"navbar",
{
"componentRef": "page-layout",
"children": [
{
"slot": "content",
"componentRef": "book-detail-view"
}
]
}
]
}
],
"tables": [
{
"slug": "books",
"name": "Cărți",
"description": "Tabela cu cărțile citite",
"fields": [
{
"slug": "title",
"name": "Titlu",
"type": "String"
},
{
"slug": "author",
"name": "Autor",
"type": "String"
},
{
"slug": "genre",
"name": "Gen",
"type": "String"
},
{
"slug": "page-count",
"name": "Număr pagini",
"type": "Number"
},
{
"slug": "date-read",
"name": "Data citirii",
"type": "Date"
},
{
"slug": "rating",
"name": "Rating",
"type": "Number"
},
{
"slug": "notes",
"name": "Notițe",
"type": "String"
}
]
}
]
}