From 313c8ad373b4a98b9bcf7873923f0131f7fdde7f Mon Sep 17 00:00:00 2001 From: EDeev Date: Mon, 29 Dec 2025 09:43:00 +0300 Subject: [PATCH] =?UTF-8?q?API=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D0=B2=D1=81=D0=BF=D0=BE=D0=BC=D0=BE=D0=B3?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D1=83=D1=82?= =?UTF-8?q?=D0=B8=D0=BB=D0=B8=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/api.js | 175 +++++++++++++++++++++++++++++++++++ js/utils.js | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 js/api.js create mode 100644 js/utils.js diff --git a/js/api.js b/js/api.js new file mode 100644 index 0000000..7cc8de2 --- /dev/null +++ b/js/api.js @@ -0,0 +1,175 @@ +const API_CONFIG = { + baseUrl: 'http://exam-api-courses.std-900.ist.mospolytech.ru/api', + apiKey: '358a63a5-52ae-4ab0-800b-90f75ce5a5c2' +}; + +// Хранилище контактных данных в локальном хранилище +const ContactDataStorage = { + // Сохранить данные контакта для заявки + save(orderId, data) { + const storageKey = `order_contact_${orderId}`; + localStorage.setItem(storageKey, JSON.stringify(data)); + }, + + // Получить данные контакта по ID заявки + get(orderId) { + const storageKey = `order_contact_${orderId}`; + const data = localStorage.getItem(storageKey); + return data ? JSON.parse(data) : null; + }, + + // Удалить данные контакта из хранилища + remove(orderId) { + const storageKey = `order_contact_${orderId}`; + localStorage.removeItem(storageKey); + } +}; + +// Добавить API-ключ к URL запроса +function addApiKey(url) { + const separator = url.includes('?') ? '&' : '?'; + return `${url}${separator}api_key=${API_CONFIG.apiKey}`; +} + +// Отправить запрос к API с обработкой ошибок +async function apiRequest(endpoint, method = 'GET', data = null) { + const url = addApiKey(`${API_CONFIG.baseUrl}${endpoint}`); + + const options = { + method, + headers: {'Content-Type': 'application/json'} + }; + + if (data && (method === 'POST' || method === 'PUT')) { + options.body = JSON.stringify(data); + } + + try { + const response = await fetch(url, options); + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || 'Ошибка сервера'); + } + + return result; + } catch (error) { + console.error('API Error:', error); + throw error; + } +} + +// Загрузить список курсов и отобразить +async function loadCourses() { + try { + AppState.courses = await apiRequest('/courses'); + AppState.filteredCourses = [...AppState.courses]; + renderCourses(); + } catch (error) { + showNotification('Ошибка загрузки курсов: ' + + error.message, 'error'); + } +} + +// Загрузить список репетиторов и отобразить +async function loadTutors() { + try { + AppState.tutors = await apiRequest('/tutors'); + AppState.filteredTutors = [...AppState.tutors]; + populateLanguageFilter(); + renderTutors(); + } catch (error) { + showNotification('Ошибка загрузки репетиторов: ' + + error.message, 'error'); + } +} + +// Загрузить список заявок и обновить интерфейс +async function loadOrders() { + try { + AppState.orders = await apiRequest('/orders'); + renderOrders(); + updateUserInfo(); + } catch (error) { + showNotification('Ошибка загрузки заявок: ' + + error.message, 'error'); + } +} + +// Создать новую заявку и сохранить контакты +async function createOrder(orderData, contactData) { + try { + const result = await apiRequest('/orders', 'POST', orderData); + ContactDataStorage.save(result.id, contactData); + showNotification('Заявка успешно создана!', 'success'); + return result; + } catch (error) { + showNotification('Ошибка создания заявки: ' + + error.message, 'error'); + throw error; + } +} + +// Обновить существующую заявку и контакты +async function updateOrder(orderId, orderData, contactData) { + try { + const result = await apiRequest(`/orders/${orderId}`, + 'PUT', orderData); + if (contactData) { + ContactDataStorage.save(orderId, contactData); + } + showNotification('Заявка успешно обновлена!', 'success'); + return result; + } catch (error) { + showNotification('Ошибка обновления заявки: ' + + error.message, 'error'); + throw error; + } +} + +// Удалить заявку и её контактные данные +async function deleteOrder(orderId) { + try { + const result = await apiRequest(`/orders/${orderId}`, 'DELETE'); + ContactDataStorage.remove(orderId); + showNotification('Заявка успешно удалена!', 'success'); + return result; + } catch (error) { + showNotification('Ошибка удаления заявки: ' + + error.message, 'error'); + throw error; + } +} + +// Получить заявку по ID +async function getOrder(orderId) { + try { + return await apiRequest(`/orders/${orderId}`); + } catch (error) { + showNotification('Ошибка получения заявки: ' + + error.message, 'error'); + throw error; + } +} + +// Получить курс по ID +async function getCourse(courseId) { + try { + return await apiRequest(`/courses/${courseId}`); + } catch (error) { + showNotification('Ошибка получения курса: ' + + error.message, 'error'); + throw error; + } +} + +// Получить репетитора по ID +async function getTutor(tutorId) { + try { + return await apiRequest(`/tutors/${tutorId}`); + } catch (error) { + showNotification('Ошибка получения репетитора: ' + + error.message, 'error'); + throw error; + } +} \ No newline at end of file diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000..fdf6704 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,258 @@ +// Показать уведомление с автоматическим удалением +function showNotification(message, type = 'info') { + const notificationArea = document.getElementById('notificationArea'); + if (!notificationArea) return; + + const alertTypes = { + 'success': 'alert-success', + 'error': 'alert-danger', + 'warning': 'alert-warning', + 'info': 'alert-info' + }; + + const icons = { + 'success': 'bi-check-circle-fill', + 'error': 'bi-exclamation-circle-fill', + 'warning': 'bi-exclamation-triangle-fill', + 'info': 'bi-info-circle-fill' + }; + + const alertClass = alertTypes[type] || 'alert-info'; + const iconClass = icons[type] || 'bi-info-circle-fill'; + + const notification = document.createElement('div'); + notification.className = `alert ${alertClass} alert-dismissible + fade show notification`; + notification.innerHTML = ` + + ${message} + + `; + + notificationArea.appendChild(notification); + + // Удалить уведомление через 5 секунд + setTimeout(() => { + notification.remove(); + }, 5000); +} + +// Форматировать цену в рублях +function formatPrice(price) { + return new Intl.NumberFormat('ru-RU').format(price) + ' ₽'; +} + +// Форматировать дату в формате ДД.ММ.ГГГГ +function formatDate(dateString) { + const date = new Date(dateString); + return date.toLocaleDateString('ru-RU', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }); +} + +// Форматировать дату и время +function formatDateTime(dateString) { + const date = new Date(dateString); + return date.toLocaleString('ru-RU', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); +} + +// Обрезать время до формата ЧЧ:ММ +function formatTime(timeString) { + return timeString.substring(0, 5); +} + +// Форматировать дату и время без секунд +function formatDateTimeWithoutSeconds(dateString, timeString) { + const date = new Date(dateString); + const formattedDate = date.toLocaleDateString('ru-RU', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }); + const formattedTime = formatTime(timeString); + return `${formattedDate} ${formattedTime}`; +} + +// Склонение числительных по русским правилам +function pluralize(number, one, few, many) { + const mod10 = number % 10; + const mod100 = number % 100; + + if (mod10 === 1 && mod100 !== 11) { + return `${number} ${one}`; + } else if (mod10 >= 2 && mod10 <= 4 && + (mod100 < 10 || mod100 >= 20)) { + return `${number} ${few}`; + } else { + return `${number} ${many}`; + } +} + +// Получить CSS класс для бейджа уровня +function getLevelBadgeClass(level) { + const levelMap = { + 'Beginner': 'beginner', + 'Intermediate': 'intermediate', + 'Advanced': 'advanced' + }; + return levelMap[level] || 'beginner'; +} + +// Перевести уровень на русский язык +function getLevelText(level) { + const levelMap = { + 'Beginner': 'Начальный', + 'Intermediate': 'Средний', + 'Advanced': 'Продвинутый' + }; + return levelMap[level] || level; +} + +// Проверить является ли день выходным +function isWeekend(date) { + const day = date.getDay(); + return day === 0 || day === 6; +} + +// Проверить является ли дата праздничным днём +function isHoliday(date) { + // Праздничные дни в России на 2025-2026 годы + const holidays = [ + // 2025 + '2025-01-01', '2025-01-02', '2025-01-03', '2025-01-04', + '2025-01-05', '2025-01-06', '2025-01-07', '2025-01-08', + '2025-02-23', '2025-03-08', '2025-05-01', '2025-05-09', + '2025-06-12', '2025-11-04', + // 2026 + '2026-01-01', '2026-01-02', '2026-01-03', '2026-01-04', + '2026-01-05', '2026-01-06', '2026-01-07', '2026-01-08', + '2026-02-23', '2026-03-08', '2026-05-01', '2026-05-09', + '2026-06-12', '2026-11-04' + ]; + + const dateStr = date.toISOString().split('T')[0]; + return holidays.includes(dateStr); +} + +// Получить множитель цены для выходных +function getWeekendMultiplier(dateString) { + const date = new Date(dateString); + return (isWeekend(date) || isHoliday(date)) ? 1.5 : 1; +} + +// Рассчитать надбавку за время занятия +function getTimeSurcharge(timeString) { + const hour = parseInt(timeString.split(':')[0]); + let surcharge = 0; + + // Утренняя надбавка + if (hour >= 9 && hour < 12) { + surcharge += 400; + } + + // Вечерняя надбавка + if (hour >= 18 && hour < 20) { + surcharge += 1000; + } + + return surcharge; +} + +// Рассчитать дату окончания курса +function calculateEndDate(startDateStr, weeks) { + const date = new Date(startDateStr); + date.setDate(date.getDate() + (weeks * 7)); + return date; +} + +// Отформатировать дату окончания курса +function formatEndDate(startDateStr, weeks) { + const endDate = calculateEndDate(startDateStr, weeks); + return formatDate(endDate.toISOString().split('T')[0]); +} + +// Проверить ранняя ли это регистрация +function isEarlyRegistration(startDate) { + const today = new Date(); + const start = new Date(startDate); + const diffTime = start - today; + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + return diffDays >= 30; +} + +// Задержка выполнения функции для оптимизации +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +function generatePagination(currentPage, totalPages, containerId) { + const container = document.getElementById(containerId); + if (!container) return; + + container.innerHTML = ''; + + if (totalPages <= 1) return; + + const ul = document.createElement('ul'); + ul.className = 'pagination justify-content-center'; + + const createPageItem = (page, text, disabled = false, + active = false) => { + const li = document.createElement('li'); + li.className = `page-item ${disabled ? 'disabled' : ''} + ${active ? 'active' : ''}`; + + const a = document.createElement('a'); + a.className = 'page-link'; + a.href = '#'; + a.textContent = text || page; + + if (!disabled) { + a.addEventListener('click', (e) => { + e.preventDefault(); + return page; + }); + } + + li.appendChild(a); + return li; + }; + + ul.appendChild(createPageItem(currentPage - 1, 'Назад', + currentPage === 1)); + + for (let i = 1; i <= totalPages; i++) { + if (i === 1 || i === totalPages || + (i >= currentPage - 1 && i <= currentPage + 1)) { + ul.appendChild(createPageItem(i, i, false, + i === currentPage)); + } else if (i === currentPage - 2 || i === currentPage + 2) { + const li = document.createElement('li'); + li.className = 'page-item disabled'; + li.innerHTML = '...'; + ul.appendChild(li); + } + } + + ul.appendChild(createPageItem(currentPage + 1, 'Вперед', + currentPage === totalPages)); + + container.appendChild(ul); +} \ No newline at end of file