API функции и вспомогательные утилиты
This commit is contained in:
parent
f324ae26a8
commit
313c8ad373
2 changed files with 433 additions and 0 deletions
175
js/api.js
Normal file
175
js/api.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
258
js/utils.js
Normal file
258
js/utils.js
Normal file
|
|
@ -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 = `
|
||||
<i class="bi ${iconClass} me-2"></i>
|
||||
${message}
|
||||
<button type="button" class="btn-close"
|
||||
data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
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 = '<span class="page-link">...</span>';
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
ul.appendChild(createPageItem(currentPage + 1, 'Вперед',
|
||||
currentPage === totalPages));
|
||||
|
||||
container.appendChild(ul);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue