/
zykovad
/
PIR_Lab1_E-Library
Обзор
Документация
Войти
/
zykovad
/
PIR_Lab1_E-Library
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
lab3/client/script.js
614 строк
20 KB
Darya
Done lab 3
08 ноя 2025, 19:53
08 ноя 2025, 19:53
c824b06
Код
Авторство
О чём код?
// Конфигурация const API_BASE_URL = 'http://127.0.0.1:8000'; let apiKey = ''; let editingId = null; let currentProjectId = null; let projects = []; //переменные для фильтров let currentFilters = { completed: null, priority_min: null, priority_max: null }; //переменные для пагинации let currentPage = 1; const tasksPerPage = 6; let hasMoreTasks = false; // Генерация уникального ключа для идемпотентности function generateIdempotencyKey() { return 'idempotent_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } // Основные функции API async function apiRequest(endpoint, options = {}) { const url = `${API_BASE_URL}${endpoint}`; const headers = { 'Content-Type': 'application/json', }; if (apiKey) { headers['X-API-Key'] = apiKey; } if (options.headers) { Object.assign(headers, options.headers); } const config = { method: options.method || 'GET', headers: headers, body: options.body }; console.log('API Request:', { url, method: config.method, headers: config.headers, body: options.body }); try { const response = await fetch(url, config); const allHeaders = {}; for (const [key, value] of response.headers.entries()) { allHeaders[key] = value; } console.log('API Response:', { status: response.status, statusText: response.statusText, headers: allHeaders }); // Обработка ошибки 429 - Too Many Requests if (response.status === 429) { const retryAfter = allHeaders['retry-after']; const waitTime = retryAfter ? parseInt(retryAfter) : 60; const errorMessage = `Слишком много запросов. Попробуйте снова через ${waitTime} сек.`; showError(errorMessage); return []; } if (config.method === 'DELETE' && response.status === 204) { return { success: true }; } if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('API Success'); return data; } catch (error) { console.error('API Request Failed:', error); if (!error.message.includes('429')) { showError(error.message); } throw error; } } // Функции для проектов async function loadProjects() { try { projects = await apiRequest('/api/v2/projects/'); renderProjectTabs(); // Автоматически выбираем первый проект if (projects.length > 0 && !currentProjectId) { selectProject(projects[0].id); } } catch (error) { showError('Не удалось загрузить проекты: ' + error.message); } } async function createProject(projectData) { await apiRequest('/api/v2/projects/', { method: 'POST', body: JSON.stringify(projectData) }); await loadProjects(); await loadTasks(currentProjectId); renderProjectHeader(); } async function updateProject(id, projectData) { await apiRequest(`/api/v2/projects/${id}`, { method: 'PUT', body: JSON.stringify(projectData) }); await loadProjects(); await loadTasks(currentProjectId); renderProjectHeader(); } async function deleteProject(id) { if (!confirm('Удалить проект?')) return; await apiRequest(`/api/v2/projects/${id}`, { method: 'DELETE' }); await loadProjects(); await loadTasks(currentProjectId); renderProjectHeader(); } // Функции для работы с фильтрами function applyFilters() { const completedFilter = document.getElementById('filter-completed').value; const priorityMin = document.getElementById('filter-priority-min').value; const priorityMax = document.getElementById('filter-priority-max').value; currentFilters.completed = completedFilter === '' ? null : completedFilter; currentFilters.priority_min = priorityMin === '' ? null : parseInt(priorityMin); currentFilters.priority_max = priorityMax === '' ? null : parseInt(priorityMax); currentPage = 1; loadTasks(currentProjectId, 1); } function resetFilters() { document.getElementById('filter-completed').value = ''; document.getElementById('filter-priority-min').value = ''; document.getElementById('filter-priority-max').value = ''; currentFilters = { completed: null, priority_min: null, priority_max: null }; currentPage = 1; loadTasks(currentProjectId, 1); } // Функции для задач async function loadTasks(projectId = null, page = 1) { try { currentPage = page; let endpoint = '/api/v3/tasks/'; const params = new URLSearchParams(); // Параметры пагинации const offset = (page - 1) * tasksPerPage; params.append('offset', offset); params.append('limit', tasksPerPage); if (projectId) { params.append('project_id', projectId); } if (currentFilters.completed !== null) { params.append('completed', currentFilters.completed); } if (currentFilters.priority_min !== null) { params.append('priority_min', currentFilters.priority_min); } if (currentFilters.priority_max !== null) { params.append('priority_max', currentFilters.priority_max); } if (params.toString()) { endpoint += `?${params.toString()}`; } const tasks = await apiRequest(endpoint); hasMoreTasks = tasks.length === tasksPerPage; renderTasks(tasks); updatePagination(); } catch (error) { showError('Не удалось загрузить задачи: ' + error.message); } } async function createTask(taskData, idempotencyKey = null) { const options = { method: 'POST', body: JSON.stringify(taskData) }; if (idempotencyKey) { options.headers = { 'Idempotency-Key': idempotencyKey }; } console.log('Creating task with idempotency key:', idempotencyKey); console.log('Task data:', taskData); result1 = await apiRequest('/api/v2/tasks/', options); result2 = await apiRequest('/api/v2/tasks/', options); console.log('result 1', result1); console.log('result 2', result2); await loadTasks(currentProjectId); } async function updateTask(id, taskData) { await apiRequest(`/api/v2/tasks/${id}`, { method: 'PUT', body: JSON.stringify(taskData) }); await loadTasks(currentProjectId); } async function deleteTask(id) { if (!confirm('Удалить задачу?')) return; await apiRequest(`/api/v2/tasks/${id}`, { method: 'DELETE' }); await loadTasks(currentProjectId); } // Рендеринг вкладок проектов function renderProjectTabs() { const tabsContainer = document.getElementById('projects-tabs'); tabsContainer.innerHTML = ''; // Вкладка "Все задачи" const allTasksTab = document.createElement('div'); allTasksTab.className = `project-tab ${!currentProjectId ? 'active' : ''}`; allTasksTab.textContent = 'Все задачи'; allTasksTab.onclick = () => selectProject(null); tabsContainer.appendChild(allTasksTab); // Вкладки проектов projects.forEach(project => { const tab = document.createElement('div'); tab.className = `project-tab ${currentProjectId === project.id ? 'active' : ''}`; tab.textContent = project.name; tab.onclick = () => selectProject(project.id); tabsContainer.appendChild(tab); }); // Кнопка добавления проекта const addTab = document.createElement('button'); addTab.className = 'add-project-tab'; addTab.textContent = '+'; addTab.title = 'Добавить проект'; addTab.onclick = openProjectModal; tabsContainer.appendChild(addTab); } function selectProject(projectId) { currentProjectId = projectId; currentPage = 1; renderProjectTabs(); renderProjectHeader(); loadTasks(projectId, 1); } // Функция для отображения заголовка и кнопок проекта function renderProjectHeader() { const projectTitle = document.getElementById('current-project-title'); const projectDescription = document.getElementById('current-project-description'); const projectActions = document.getElementById('project-actions'); if (currentProjectId && projects.length > 0) { const project = projects.find(p => p.id === currentProjectId); if (project) { projectTitle.textContent = project.name; projectDescription.textContent = project.description || 'Описание отсутствует'; projectDescription.style.display = 'block'; // Создаем кнопки действий для проекта projectActions.innerHTML = ` <button class="project-action-btn edit-project-btn" onclick="editProject(${project.id}, '${escapeHtml(project.name)}', '${escapeHtml(project.description || '')}')"> Редактировать проект </button> <button class="project-action-btn delete-project-btn" onclick="deleteProject(${project.id})"> Удалить проект </button> `; projectActions.style.display = 'flex'; return; } } // Для раздела "Все задачи" projectTitle.textContent = 'Все задачи'; projectDescription.textContent = ''; projectDescription.style.display = 'none'; projectActions.innerHTML = ''; projectActions.style.display = 'none'; } // Рендеринг задач function renderTasks(tasks) { const list = document.getElementById('tasks-list'); if (!tasks || tasks.length === 0) { list.innerHTML = '<p class="no-items">Задач пока нет</p>'; return; } list.innerHTML = tasks.map(task => { // Находим проект для этой задачи const project = projects.find(p => p.id === task.project_id); const projectName = project ? project.name : 'Неизвестный проект'; return ` <div class="item-card ${task.completed ? 'completed' : ''}"> <h3>${escapeHtml(task.title)}</h3> <div class="task-info"> <p><strong>Проект:</strong> ${escapeHtml(projectName)}</p> <p><strong>Приоритет:</strong> ${task.priority || 0}</p> <p><strong>Статус:</strong> ${task.completed ? 'Выполнена' : 'В работе'}</p> </div> <div class="item-actions"> <button class="edit-btn" onclick="editTask(${task.id}, '${escapeHtml(task.title)}', ${task.priority || 0}, ${task.completed})">Редактировать</button> <button class="delete-btn" onclick="deleteTask(${task.id})">Удалить</button> </div> </div> `; }).join(''); } // Управление интерфейсом function login() { apiKey = document.getElementById('api-key').value.trim(); if (!apiKey) { showError('Введите API ключ'); return; } document.getElementById('login-screen').classList.remove('active'); document.getElementById('main-screen').classList.add('active'); loadProjects(); } function logout() { apiKey = ''; currentProjectId = null; projects = []; document.getElementById('main-screen').classList.remove('active'); document.getElementById('login-screen').classList.add('active'); document.getElementById('api-key').value = ''; } // Модальные окна проектов function openProjectModal() { editingId = null; document.getElementById('project-modal-title').textContent = 'Добавить проект'; document.getElementById('project-name').value = ''; document.getElementById('project-description').value = ''; document.getElementById('project-modal').classList.add('active'); } function closeProjectModal() { document.getElementById('project-modal').classList.remove('active'); } function editProject(id, name, description) { editingId = id; document.getElementById('project-modal-title').textContent = 'Редактировать проект'; document.getElementById('project-name').value = name; document.getElementById('project-description').value = description; document.getElementById('project-modal').classList.add('active'); } async function saveProject() { const name = document.getElementById('project-name').value.trim(); const description = document.getElementById('project-description').value.trim(); if (!name) { showError('Введите название проекта'); return; } const projectData = { name, description: description || null }; try { if (editingId) { await updateProject(editingId, projectData); } else { await createProject(projectData); } closeProjectModal(); } catch (error) { showError('Ошибка сохранения проекта: ' + error.message); } } // Модальные окна задач async function openTaskModal() { editingId = null; document.getElementById('task-modal-title').textContent = 'Добавить задачу'; document.getElementById('task-title').value = ''; document.getElementById('task-priority').value = '0'; document.getElementById('task-completed').checked = false; await populateProjectSelect(); if (currentProjectId) { document.getElementById('task-project').value = currentProjectId; } document.getElementById('task-modal').classList.add('active'); } function closeTaskModal() { document.getElementById('task-modal').classList.remove('active'); } async function editTask(id, title, priority, completed) { editingId = id; document.getElementById('task-modal-title').textContent = 'Редактировать задачу'; document.getElementById('task-title').value = title; document.getElementById('task-priority').value = priority; document.getElementById('task-completed').checked = completed; // Загружаем проекты и устанавливаем текущий проект задачи await populateProjectSelect(); // Получаем задачу чтобы узнать её проект try { const task = await apiRequest(`/api/v2/tasks/${id}`); document.getElementById('task-project').value = task.project_id; } catch (error) { console.error('Не удалось загрузить данные задачи:', error); // Если не удалось загрузить задачу, используем текущий выбранный проект if (currentProjectId) { document.getElementById('task-project').value = currentProjectId; } } document.getElementById('task-modal').classList.add('active'); } async function populateProjectSelect() { const select = document.getElementById('task-project'); // Сохраняем текущее значение const currentValue = select.value; // Очищаем список select.innerHTML = '<option value="">Выберите проект</option>'; // Загружаем проекты если еще не загружены if (projects.length === 0) { try { projects = await apiRequest('/api/v2/projects/'); } catch (error) { console.error('Не удалось загрузить проекты:', error); return; } } // Заполняем список проектов projects.forEach(project => { const option = document.createElement('option'); option.value = project.id; option.textContent = project.name; select.appendChild(option); }); // Восстанавливаем предыдущее значение если нужно if (currentValue) { select.value = currentValue; } } async function saveTask() { const title = document.getElementById('task-title').value.trim(); const projectId = document.getElementById('task-project').value; const priority = parseInt(document.getElementById('task-priority').value) || 0; const completed = document.getElementById('task-completed').checked; if (!title) { showError('Введите название задачи'); return; } if (!projectId) { showError('Выберите проект для задачи'); return; } const taskData = { title, project_id: parseInt(projectId), priority, completed }; try { if (editingId) { await updateTask(editingId, taskData); } else { const idempotencyKey = generateIdempotencyKey(); await createTask(taskData, idempotencyKey); } closeTaskModal(); } catch (error) { showError('Ошибка сохранения задачи: ' + error.message); } } // Вспомогательные функции function escapeHtml(unsafe) { if (typeof unsafe !== 'string') return unsafe; return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function showError(message) { alert(message); } // Обработчики событий document.addEventListener('DOMContentLoaded', function() { document.getElementById('api-key').addEventListener('keypress', function(e) { if (e.key === 'Enter') login(); }); document.getElementById('login-btn').addEventListener('click', login); document.getElementById('logout-btn').addEventListener('click', logout); document.getElementById('add-task-btn').addEventListener('click', openTaskModal); document.getElementById('cancel-project-btn').addEventListener('click', closeProjectModal); document.getElementById('cancel-task-btn').addEventListener('click', closeTaskModal); document.getElementById('project-form').addEventListener('submit', function(e) { e.preventDefault(); saveProject(); }); document.getElementById('task-form').addEventListener('submit', function(e) { e.preventDefault(); saveTask(); }); document.getElementById('apply-filters').addEventListener('click', applyFilters); document.getElementById('reset-filters').addEventListener('click', resetFilters); document.getElementById('filter-priority-min').addEventListener('keypress', function(e) { if (e.key === 'Enter') applyFilters(); }); document.getElementById('filter-priority-max').addEventListener('keypress', function(e) { if (e.key === 'Enter') applyFilters(); }); document.getElementById('next-page').addEventListener('click', nextPage); document.getElementById('prev-page').addEventListener('click', prevPage); }); function updatePagination() { const prevButton = document.getElementById('prev-page'); const nextButton = document.getElementById('next-page'); const pageInfo = document.getElementById('page-info'); // Обновляем информацию о странице pageInfo.textContent = `Страница ${currentPage}`; // Обновляем состояние кнопок prevButton.disabled = currentPage === 1; nextButton.disabled = !hasMoreTasks; } // Функции для переключения страниц function nextPage() { if (hasMoreTasks) { loadTasks(currentProjectId, currentPage + 1); } } function prevPage() { if (currentPage > 1) { loadTasks(currentProjectId, currentPage - 1); } }