biblioteka-faylov-sozdat-pap/index.html

318 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" data-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Библиотека документов</title>
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
<link rel="stylesheet" href="design-system/kt-ai-components.css">
<link rel="stylesheet" href="design-system/kt-ai-page.css">
<style>
.library-container { display: flex; gap: var(--kt-ai-space-6); }
.sidebar { width: 280px; flex-shrink: 0; }
.main-content { flex: 1; min-width: 0; }
.folder-item, .file-item {
display: flex; align-items: center; justify-content: space-between;
padding: var(--kt-ai-space-3) var(--kt-ai-space-4);
border-radius: var(--kt-ai-radius-md);
cursor: pointer; transition: background 0.2s;
}
.folder-item:hover, .file-item:hover { background: var(--kt-ai-bg-soft); }
.folder-item.active { background: var(--kt-ai-primary); color: white; }
.folder-icon { width: 20px; height: 20px; margin-right: var(--kt-ai-space-3); fill: currentColor; }
.file-icon { width: 20px; height: 20px; margin-right: var(--kt-ai-space-3); fill: currentColor; }
.delete-btn {
opacity: 0; background: none; border: none; cursor: pointer;
padding: var(--kt-ai-space-1); color: var(--kt-ai-fg-muted);
}
.folder-item:hover .delete-btn, .file-item:hover .delete-btn { opacity: 1; }
.delete-btn:hover { color: var(--kt-ai-danger); }
.empty-state {
text-align: center; padding: var(--kt-ai-space-12);
color: var(--kt-ai-fg-muted);
}
.file-list { display: flex; flex-direction: column; gap: var(--kt-ai-space-2); }
.file-meta { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
.folder-item.active .file-meta { color: rgba(255,255,255,0.7); }
.controls { display: flex; gap: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-6); flex-wrap: wrap; }
.search-input { flex: 1; min-width: 200px; }
.sort-select { min-width: 180px; }
.upload-form { display: none; padding: var(--kt-ai-space-4); background: var(--kt-ai-bg-soft); border-radius: var(--kt-ai-radius-lg); margin-bottom: var(--kt-ai-space-4); }
.upload-form.active { display: block; }
.form-row { display: flex; gap: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-3); flex-wrap: wrap; }
.form-row input, .form-row select { flex: 1; min-width: 150px; }
.doc-badge {
display: inline-block; padding: 2px 8px; border-radius: var(--kt-ai-radius-md);
font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-medium);
background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg-muted);
}
</style>
</head>
<body>
<header class="kt-ai-pagehead">
<div class="kt-ai-wrap kt-ai-pagehead-in">
<strong>📄 Библиотека документов</strong>
</div>
</header>
<section class="kt-ai-section">
<div class="kt-ai-wrap">
<div class="library-container">
<aside class="sidebar">
<div class="kt-ai-card" style="padding: var(--kt-ai-space-4)">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--kt-ai-space-4)">
<h3 style="margin:0">Категории</h3>
<button class="kt-ai-btn" data-variant="primary" data-size="sm" id="newFolderBtn">+</button>
</div>
<div id="folderList"></div>
</div>
</aside>
<main class="main-content">
<div class="kt-ai-card" style="padding: var(--kt-ai-space-6)">
<div class="controls">
<input class="kt-ai-input search-input" id="searchInput" placeholder="Поиск документа...">
<select class="kt-ai-select sort-select" id="sortSelect">
<option value="date-desc">Сначала новые</option>
<option value="date-asc">Сначала старые</option>
<option value="name-asc">По имени (А-Я)</option>
</select>
<button class="kt-ai-btn" data-variant="primary" id="toggleUploadBtn">+ Документ</button>
</div>
<div class="upload-form" id="uploadForm">
<h4 style="margin:0 0 var(--kt-ai-space-3) 0">Загрузка документа</h4>
<div class="form-row">
<input class="kt-ai-input" id="fileName" placeholder="Название документа*" required>
<select class="kt-ai-select" id="fileFolder">
<option value="">Выберите категорию</option>
</select>
</div>
<div class="form-row">
<input class="kt-ai-input" id="fileType" placeholder="Тип (pdf, docx, xlsx...)" style="max-width:150px">
<input class="kt-ai-input" id="fileSize" placeholder="Размер (КБ)" type="number" style="max-width:120px">
</div>
<div class="form-row">
<button class="kt-ai-btn" data-variant="primary" id="submitUploadBtn">Загрузить</button>
<button class="kt-ai-btn" data-variant="ghost" id="cancelUploadBtn">Отмена</button>
</div>
</div>
<h2 id="currentFolderTitle" style="margin-bottom: var(--kt-ai-space-4)">Все документы</h2>
<div id="fileList" class="file-list"></div>
</div>
</main>
</div>
</div>
</section>
<footer class="kt-ai-wrap kt-ai-pagefoot">
<span>© 2026 Библиотека документов</span>
</footer>
<script>
let folders = [];
let files = [];
let currentFolderId = null;
let searchTerm = '';
let sortMode = 'date-desc';
async function loadFolders() {
const res = await fetch('/api/folders');
folders = await res.json();
renderFolders();
updateFolderSelect();
}
async function loadFiles() {
const res = await fetch('/api/files');
files = await res.json();
renderFiles();
}
function renderFolders() {
const container = document.getElementById('folderList');
container.innerHTML = folders.map(f => `
<div class="folder-item ${currentFolderId === f.id ? 'active' : ''}" onclick="selectFolder(${f.id})">
<div style="display:flex;align-items:center;flex:1">
<svg class="folder-icon" viewBox="0 0 24 24"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
<span>${f.name}</span>
</div>
<button class="delete-btn" onclick="event.stopPropagation(); deleteFolder(${f.id})">✕</button>
</div>
`).join('');
}
function updateFolderSelect() {
const select = document.getElementById('fileFolder');
select.innerHTML = '<option value="">Выберите категорию</option>' +
folders.map(f => `<option value="${f.id}">${f.name}</option>`).join('');
}
function renderFiles() {
const container = document.getElementById('fileList');
let filtered = files.filter(f => {
if (currentFolderId !== null && f.folderId !== currentFolderId) return false;
if (searchTerm && !f.name.toLowerCase().includes(searchTerm.toLowerCase())) return false;
return true;
});
filtered.sort((a, b) => {
const dateA = new Date(a.createdAt);
const dateB = new Date(b.createdAt);
if (sortMode === 'date-desc') return dateB - dateA;
if (sortMode === 'date-asc') return dateA - dateB;
if (sortMode === 'name-asc') return a.name.localeCompare(b.name);
return 0;
});
if (filtered.length === 0) {
container.innerHTML = '<div class="empty-state">Нет документов</div>';
return;
}
container.innerHTML = filtered.map(f => {
const folder = folders.find(fl => fl.id === f.folderId);
const size = formatSize(f.size);
const date = new Date(f.createdAt).toLocaleDateString('ru-RU');
const ext = getFileExtension(f.name);
return `
<div class="file-item">
<div style="display:flex;align-items:center;flex:1">
<svg class="file-icon" viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>
<div>
<div style="display:flex;align-items:center;gap:var(--kt-ai-space-2)">
<span>${f.name}</span>
${ext ? '<span class="doc-badge">' + ext.toUpperCase() + '</span>' : ''}
</div>
<div class="file-meta">${size} • ${date}${folder ? ' • ' + folder.name : ''}</div>
</div>
</div>
<button class="delete-btn" onclick="deleteFile(${f.id})">✕</button>
</div>
`;
}).join('');
}
function getFileExtension(name) {
const parts = (name || '').split('.');
return parts.length > 1 ? parts.pop() : '';
}
function formatSize(kb) {
if (!kb) return '';
if (kb < 1024) return kb + ' КБ';
return (kb / 1024).toFixed(1) + ' МБ';
}
function selectFolder(id) {
currentFolderId = id;
const folder = folders.find(f => f.id === id);
document.getElementById('currentFolderTitle').textContent = folder ? folder.name : 'Все документы';
renderFolders();
renderFiles();
}
async function createFolder() {
const name = prompt('Название категории:');
if (!name || !name.trim()) return;
try {
const res = await fetch('/api/folders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name.trim() })
});
if (!res.ok) throw new Error('Ошибка: ' + res.status);
await loadFolders();
} catch (err) {
alert('Не удалось создать категорию: ' + err.message);
}
}
async function deleteFolder(id) {
if (!confirm('Удалить категорию и все документы в ней?')) return;
await fetch('/api/folders/' + id, { method: 'DELETE' });
if (currentFolderId === id) {
currentFolderId = null;
document.getElementById('currentFolderTitle').textContent = 'Все документы';
}
await loadFolders();
await loadFiles();
}
async function deleteFile(id) {
if (!confirm('Удалить документ?')) return;
await fetch('/api/files/' + id, { method: 'DELETE' });
await loadFiles();
}
document.getElementById('newFolderBtn').addEventListener('click', createFolder);
document.getElementById('searchInput').addEventListener('input', (e) => {
searchTerm = e.target.value;
renderFiles();
});
document.getElementById('sortSelect').addEventListener('change', (e) => {
sortMode = e.target.value;
renderFiles();
});
document.getElementById('toggleUploadBtn').addEventListener('click', () => {
if (folders.length === 0) {
alert('Сначала создайте категорию');
return;
}
document.getElementById('uploadForm').classList.toggle('active');
});
document.getElementById('cancelUploadBtn').addEventListener('click', () => {
document.getElementById('uploadForm').classList.remove('active');
document.getElementById('fileName').value = '';
document.getElementById('fileSize').value = '';
document.getElementById('fileType').value = '';
});
document.getElementById('submitUploadBtn').addEventListener('click', async () => {
const name = document.getElementById('fileName').value.trim();
const folderId = parseInt(document.getElementById('fileFolder').value);
const size = parseFloat(document.getElementById('fileSize').value) || 0;
const type = document.getElementById('fileType').value.trim();
if (!name) {
alert('Введите название документа');
return;
}
if (!folderId) {
alert('Выберите категорию');
return;
}
const finalName = type && !name.includes('.') ? name + '.' + type : name;
try {
const res = await fetch('/api/files', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ folderId, name: finalName, size })
});
if (!res.ok) throw new Error('Ошибка загрузки');
document.getElementById('uploadForm').classList.remove('active');
document.getElementById('fileName').value = '';
document.getElementById('fileSize').value = '';
document.getElementById('fileType').value = '';
await loadFiles();
} catch (err) {
alert('Ошибка: ' + err.message);
}
});
loadFolders();
loadFiles();
</script>
</body>
</html>