Основная логика приложения в app.js
This commit is contained in:
parent
313c8ad373
commit
7f8478e25e
1 changed files with 756 additions and 0 deletions
756
js/app.js
Normal file
756
js/app.js
Normal file
|
|
@ -0,0 +1,756 @@
|
||||||
|
const AppState = {
|
||||||
|
courses: [],
|
||||||
|
tutors: [],
|
||||||
|
orders: [],
|
||||||
|
filteredCourses: [],
|
||||||
|
filteredTutors: [],
|
||||||
|
selectedCourse: null,
|
||||||
|
selectedTutor: null,
|
||||||
|
currentCoursePage: 1,
|
||||||
|
coursesPerPage: 3,
|
||||||
|
currentTutorPage: 1,
|
||||||
|
tutorsPerPage: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initApp();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Инициализировать главную страницу приложения
|
||||||
|
async function initApp() {
|
||||||
|
await loadCourses();
|
||||||
|
await loadTutors();
|
||||||
|
setupEventListeners();
|
||||||
|
initMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Настроить все обработчики событий
|
||||||
|
function setupEventListeners() {
|
||||||
|
const courseSearch = document.getElementById('courseSearch');
|
||||||
|
const levelFilter = document.getElementById('levelFilter');
|
||||||
|
const sortCourses = document.getElementById('sortCourses');
|
||||||
|
|
||||||
|
if (courseSearch) {
|
||||||
|
courseSearch.addEventListener('input',
|
||||||
|
debounce(filterAndRenderCourses, 300));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (levelFilter) {
|
||||||
|
levelFilter.addEventListener('change', filterAndRenderCourses);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortCourses) {
|
||||||
|
sortCourses.addEventListener('change', filterAndRenderCourses);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tutorLevelFilter =
|
||||||
|
document.getElementById('tutorLevelFilter');
|
||||||
|
const minExperience = document.getElementById('minExperience');
|
||||||
|
const languageFilter = document.getElementById('languageFilter');
|
||||||
|
const sortTutors = document.getElementById('sortTutors');
|
||||||
|
|
||||||
|
if (tutorLevelFilter) {
|
||||||
|
tutorLevelFilter.addEventListener('change',
|
||||||
|
filterAndRenderTutors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minExperience) {
|
||||||
|
minExperience.addEventListener('input',
|
||||||
|
debounce(filterAndRenderTutors, 300));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageFilter) {
|
||||||
|
languageFilter.addEventListener('change',
|
||||||
|
filterAndRenderTutors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortTutors) {
|
||||||
|
sortTutors.addEventListener('change', filterAndRenderTutors);
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitCourseOrder =
|
||||||
|
document.getElementById('submitCourseOrder');
|
||||||
|
if (submitCourseOrder) {
|
||||||
|
submitCourseOrder.addEventListener('click', submitCourseOrderForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitTutorOrder =
|
||||||
|
document.getElementById('submitTutorOrder');
|
||||||
|
if (submitTutorOrder) {
|
||||||
|
submitTutorOrder.addEventListener('click', submitTutorOrderForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseStartDate = document.getElementById('courseStartDate');
|
||||||
|
if (courseStartDate) {
|
||||||
|
courseStartDate.addEventListener('change',
|
||||||
|
updateCourseTimeSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseStartTime = document.getElementById('courseStartTime');
|
||||||
|
if (courseStartTime) {
|
||||||
|
courseStartTime.addEventListener('change', calculateCoursePrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
const studentsNumber = document.getElementById('studentsNumber');
|
||||||
|
if (studentsNumber) {
|
||||||
|
studentsNumber.addEventListener('input', calculateCoursePrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkboxes = ['supplementary', 'personalized', 'excursions',
|
||||||
|
'assessment', 'interactive'];
|
||||||
|
checkboxes.forEach(id => {
|
||||||
|
const checkbox = document.getElementById(id);
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.addEventListener('change', calculateCoursePrice);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отфильтровать и отобразить курсы
|
||||||
|
function filterAndRenderCourses() {
|
||||||
|
const searchTerm = document.getElementById('courseSearch').value
|
||||||
|
.toLowerCase();
|
||||||
|
const levelFilter = document.getElementById('levelFilter').value;
|
||||||
|
const sortOption = document.getElementById('sortCourses').value;
|
||||||
|
|
||||||
|
AppState.filteredCourses = AppState.courses.filter(course => {
|
||||||
|
const matchesSearch = course.name.toLowerCase()
|
||||||
|
.includes(searchTerm) ||
|
||||||
|
course.description.toLowerCase().includes(searchTerm);
|
||||||
|
const matchesLevel = !levelFilter ||
|
||||||
|
course.level === levelFilter;
|
||||||
|
return matchesSearch && matchesLevel;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sortOption) {
|
||||||
|
const [field, order] = sortOption.split('_');
|
||||||
|
AppState.filteredCourses.sort((a, b) => {
|
||||||
|
let aVal, bVal;
|
||||||
|
|
||||||
|
if (field === 'duration') {
|
||||||
|
aVal = a.total_length;
|
||||||
|
bVal = b.total_length;
|
||||||
|
} else if (field === 'price') {
|
||||||
|
aVal = a.course_fee_per_hour;
|
||||||
|
bVal = b.course_fee_per_hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
return order === 'asc' ? aVal - bVal : bVal - aVal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AppState.currentCoursePage = 1;
|
||||||
|
renderCourses();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCourses() {
|
||||||
|
const container = document.getElementById('coursesList');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
const startIdx = (AppState.currentCoursePage - 1) *
|
||||||
|
AppState.coursesPerPage;
|
||||||
|
const endIdx = startIdx + AppState.coursesPerPage;
|
||||||
|
const coursesToShow = AppState.filteredCourses
|
||||||
|
.slice(startIdx, endIdx);
|
||||||
|
|
||||||
|
coursesToShow.forEach(course => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'course-item';
|
||||||
|
item.dataset.courseId = course.id;
|
||||||
|
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="course-item-header">
|
||||||
|
<h5 class="course-item-title">${course.name}</h5>
|
||||||
|
<span class="badge level-badge
|
||||||
|
${getLevelBadgeClass(course.level)}">
|
||||||
|
${getLevelText(course.level)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="course-item-body">
|
||||||
|
${course.description.substring(0, 150)}...
|
||||||
|
</div>
|
||||||
|
<div class="course-item-details">
|
||||||
|
<div>
|
||||||
|
<i class="bi bi-person"></i>
|
||||||
|
<span>${course.teacher}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<i class="bi bi-clock"></i>
|
||||||
|
<span>${course.total_length} недель,
|
||||||
|
${course.week_length} ч/нед</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<i class="bi bi-cash"></i>
|
||||||
|
<span>${formatPrice(course.course_fee_per_hour)}/час</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<button class="btn btn-primary btn-sm select-course-btn">
|
||||||
|
Выбрать
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
item.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('.select-course-btn')) {
|
||||||
|
selectCourse(course);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectBtn = item.querySelector('.select-course-btn');
|
||||||
|
selectBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
selectCourse(course);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderCoursePagination();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCoursePagination() {
|
||||||
|
const container = document.getElementById('coursesPagination');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(AppState.filteredCourses.length /
|
||||||
|
AppState.coursesPerPage);
|
||||||
|
|
||||||
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
|
const createPageItem = (page, text, disabled = false) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = `page-item ${disabled ? 'disabled' : ''}
|
||||||
|
${page === AppState.currentCoursePage ?
|
||||||
|
'active' : ''}`;
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.className = 'page-link';
|
||||||
|
a.href = '#courses';
|
||||||
|
a.textContent = text || page;
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
a.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
AppState.currentCoursePage = page;
|
||||||
|
renderCourses();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
li.appendChild(a);
|
||||||
|
return li;
|
||||||
|
};
|
||||||
|
|
||||||
|
container.appendChild(
|
||||||
|
createPageItem(AppState.currentCoursePage - 1, 'Назад',
|
||||||
|
AppState.currentCoursePage === 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
if (i === 1 || i === totalPages ||
|
||||||
|
(i >= AppState.currentCoursePage - 1 &&
|
||||||
|
i <= AppState.currentCoursePage + 1)) {
|
||||||
|
container.appendChild(createPageItem(i, i, false));
|
||||||
|
} else if (i === AppState.currentCoursePage - 2 ||
|
||||||
|
i === AppState.currentCoursePage + 2) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'page-item disabled';
|
||||||
|
li.innerHTML = '<span class="page-link">...</span>';
|
||||||
|
container.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(
|
||||||
|
createPageItem(AppState.currentCoursePage + 1, 'Вперед',
|
||||||
|
AppState.currentCoursePage === totalPages)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выбрать курс и открыть форму заказа
|
||||||
|
function selectCourse(course) {
|
||||||
|
document.querySelectorAll('.course-item').forEach(item => {
|
||||||
|
item.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedItem = document.querySelector(
|
||||||
|
`.course-item[data-course-id="${course.id}"]`
|
||||||
|
);
|
||||||
|
if (selectedItem) {
|
||||||
|
selectedItem.classList.add('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
AppState.selectedCourse = course;
|
||||||
|
openCourseModal(course);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Открыть модальное окно заказа курса
|
||||||
|
function openCourseModal(course) {
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('courseModal')
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById('courseId').value = course.id;
|
||||||
|
document.getElementById('courseName').value = course.name;
|
||||||
|
document.getElementById('courseTeacher').value = course.teacher;
|
||||||
|
document.getElementById('orderMode').value = 'create';
|
||||||
|
|
||||||
|
// Получить уникальные даты (без времени)
|
||||||
|
const uniqueDates = [...new Set(course.start_dates.map(dt => dt.split('T')[0]))];
|
||||||
|
|
||||||
|
const startDateSelect = document.getElementById('courseStartDate');
|
||||||
|
startDateSelect.innerHTML = '<option value="">Выберите дату</option>';
|
||||||
|
|
||||||
|
uniqueDates.forEach(dateStr => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = dateStr;
|
||||||
|
option.textContent = formatDate(dateStr);
|
||||||
|
startDateSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
const durationText = `${course.total_length} недель,
|
||||||
|
${course.week_length} ч/нед`;
|
||||||
|
document.getElementById('courseDuration').value = durationText;
|
||||||
|
|
||||||
|
document.getElementById('studentsNumber').value = 1;
|
||||||
|
|
||||||
|
['earlyRegistration', 'groupEnrollment',
|
||||||
|
'intensiveCourse'].forEach(id => {
|
||||||
|
document.getElementById(id).checked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
['supplementary', 'personalized', 'excursions',
|
||||||
|
'assessment', 'interactive'].forEach(id => {
|
||||||
|
document.getElementById(id).checked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
['studentName', 'studentPhone', 'studentEmail',
|
||||||
|
'studentMessage'].forEach(id => {
|
||||||
|
document.getElementById(id).value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('courseStartTime').disabled = true;
|
||||||
|
document.getElementById('courseStartTime').innerHTML =
|
||||||
|
'<option value="">Сначала выберите дату</option>';
|
||||||
|
|
||||||
|
calculateCoursePrice();
|
||||||
|
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCourseTimeSlots() {
|
||||||
|
const selectedDate = document.getElementById('courseStartDate').value;
|
||||||
|
const timeSelect = document.getElementById('courseStartTime');
|
||||||
|
|
||||||
|
if (!selectedDate) {
|
||||||
|
timeSelect.disabled = true;
|
||||||
|
timeSelect.innerHTML = '<option value="">Сначала выберите дату</option>';
|
||||||
|
document.getElementById('courseEndDate').value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeSelect.disabled = false;
|
||||||
|
timeSelect.innerHTML = '';
|
||||||
|
|
||||||
|
const course = AppState.selectedCourse;
|
||||||
|
|
||||||
|
// Отфильтровать времена для выбранной даты
|
||||||
|
const timesForDate = course.start_dates
|
||||||
|
.filter(dt => dt.split('T')[0] === selectedDate)
|
||||||
|
.map(dt => dt.split('T')[1].substring(0, 5));
|
||||||
|
|
||||||
|
// Добавить опции времени с надбавками
|
||||||
|
timesForDate.forEach(timeStr => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = timeStr;
|
||||||
|
|
||||||
|
// Рассчитать время окончания занятия
|
||||||
|
const startDateTime = new Date(`${selectedDate}T${timeStr}`);
|
||||||
|
const endDateTime = new Date(startDateTime);
|
||||||
|
endDateTime.setHours(endDateTime.getHours() + course.week_length);
|
||||||
|
const endTimeStr = endDateTime.toTimeString().substring(0, 5);
|
||||||
|
|
||||||
|
// Получить надбавку за время
|
||||||
|
const surcharge = getTimeSurcharge(timeStr);
|
||||||
|
let surchargeText = '';
|
||||||
|
if (surcharge > 0) {
|
||||||
|
surchargeText = ` (+${surcharge}₽)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
option.textContent = `${timeStr} - ${endTimeStr}${surchargeText}`;
|
||||||
|
timeSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обновить дату окончания курса
|
||||||
|
const endDate = formatEndDate(selectedDate, course.total_length);
|
||||||
|
document.getElementById('courseEndDate').value = endDate;
|
||||||
|
|
||||||
|
calculateCoursePrice();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Рассчитать итоговую цену курса с надбавками
|
||||||
|
function calculateCoursePrice() {
|
||||||
|
const course = AppState.selectedCourse;
|
||||||
|
if (!course) return;
|
||||||
|
|
||||||
|
const startDate = document.getElementById('courseStartDate').value;
|
||||||
|
const startTime = document.getElementById('courseStartTime').value;
|
||||||
|
const persons = parseInt(
|
||||||
|
document.getElementById('studentsNumber').value
|
||||||
|
) || 1;
|
||||||
|
|
||||||
|
if (!startDate || !startTime) {
|
||||||
|
document.getElementById('totalPrice').textContent = '0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseFeePerHour = course.course_fee_per_hour;
|
||||||
|
const totalHours = course.total_length * course.week_length;
|
||||||
|
const weekendMultiplier = getWeekendMultiplier(startDate);
|
||||||
|
const timeSurcharge = getTimeSurcharge(startTime);
|
||||||
|
|
||||||
|
let basePrice = (courseFeePerHour * totalHours *
|
||||||
|
weekendMultiplier + timeSurcharge) * persons;
|
||||||
|
|
||||||
|
const earlyReg = isEarlyRegistration(startDate);
|
||||||
|
const groupEnroll = persons >= 5;
|
||||||
|
const intensive = course.week_length > 20;
|
||||||
|
|
||||||
|
document.getElementById('earlyRegistration').checked = earlyReg;
|
||||||
|
document.getElementById('groupEnrollment').checked = groupEnroll;
|
||||||
|
document.getElementById('intensiveCourse').checked = intensive;
|
||||||
|
|
||||||
|
if (earlyReg) basePrice *= 0.9;
|
||||||
|
if (groupEnroll) basePrice *= 0.85;
|
||||||
|
if (intensive) basePrice *= 1.2;
|
||||||
|
|
||||||
|
if (document.getElementById('supplementary').checked) {
|
||||||
|
basePrice += 2000 * persons;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('personalized').checked) {
|
||||||
|
basePrice += 1500 * course.total_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('excursions').checked) {
|
||||||
|
basePrice *= 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('assessment').checked) {
|
||||||
|
basePrice += 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('interactive').checked) {
|
||||||
|
basePrice *= 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('totalPrice').textContent =
|
||||||
|
Math.round(basePrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправить форму заказа курса на сервер
|
||||||
|
async function submitCourseOrderForm() {
|
||||||
|
const form = document.getElementById('courseOrderForm');
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseId = parseInt(document.getElementById('courseId').value);
|
||||||
|
const startDate = document.getElementById('courseStartDate').value
|
||||||
|
.split('T')[0];
|
||||||
|
const startTime = document.getElementById('courseStartTime').value;
|
||||||
|
const persons = parseInt(
|
||||||
|
document.getElementById('studentsNumber').value
|
||||||
|
);
|
||||||
|
const price = parseInt(
|
||||||
|
document.getElementById('totalPrice').textContent
|
||||||
|
);
|
||||||
|
|
||||||
|
const orderData = {
|
||||||
|
course_id: courseId,
|
||||||
|
tutor_id: 0,
|
||||||
|
date_start: startDate,
|
||||||
|
time_start: startTime,
|
||||||
|
duration: AppState.selectedCourse.total_length *
|
||||||
|
AppState.selectedCourse.week_length,
|
||||||
|
persons: persons,
|
||||||
|
price: price,
|
||||||
|
early_registration:
|
||||||
|
document.getElementById('earlyRegistration').checked,
|
||||||
|
group_enrollment:
|
||||||
|
document.getElementById('groupEnrollment').checked,
|
||||||
|
intensive_course:
|
||||||
|
document.getElementById('intensiveCourse').checked,
|
||||||
|
supplementary: document.getElementById('supplementary').checked,
|
||||||
|
personalized: document.getElementById('personalized').checked,
|
||||||
|
excursions: document.getElementById('excursions').checked,
|
||||||
|
assessment: document.getElementById('assessment').checked,
|
||||||
|
interactive: document.getElementById('interactive').checked
|
||||||
|
};
|
||||||
|
|
||||||
|
const contactData = {
|
||||||
|
name: document.getElementById('studentName').value,
|
||||||
|
phone: document.getElementById('studentPhone').value,
|
||||||
|
email: document.getElementById('studentEmail').value,
|
||||||
|
message: document.getElementById('studentMessage').value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createOrder(orderData, contactData);
|
||||||
|
const modal = bootstrap.Modal.getInstance(
|
||||||
|
document.getElementById('courseModal')
|
||||||
|
);
|
||||||
|
modal.hide();
|
||||||
|
form.reset();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating order:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateLanguageFilter() {
|
||||||
|
const languageFilter = document.getElementById('languageFilter');
|
||||||
|
if (!languageFilter) return;
|
||||||
|
|
||||||
|
const languages = new Set();
|
||||||
|
AppState.tutors.forEach(tutor => {
|
||||||
|
tutor.languages_offered.forEach(lang => languages.add(lang));
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(languages).sort().forEach(lang => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = lang;
|
||||||
|
option.textContent = lang;
|
||||||
|
languageFilter.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отфильтровать и отобразить репетиторов
|
||||||
|
function filterAndRenderTutors() {
|
||||||
|
const levelFilter =
|
||||||
|
document.getElementById('tutorLevelFilter').value;
|
||||||
|
const minExp = parseInt(
|
||||||
|
document.getElementById('minExperience').value
|
||||||
|
) || 0;
|
||||||
|
const langFilter = document.getElementById('languageFilter').value;
|
||||||
|
const sortOption = document.getElementById('sortTutors').value;
|
||||||
|
|
||||||
|
AppState.filteredTutors = AppState.tutors.filter(tutor => {
|
||||||
|
const matchesLevel = !levelFilter ||
|
||||||
|
tutor.language_level === levelFilter;
|
||||||
|
const matchesExp = tutor.work_experience >= minExp;
|
||||||
|
const matchesLang = !langFilter ||
|
||||||
|
tutor.languages_offered.includes(langFilter);
|
||||||
|
return matchesLevel && matchesExp && matchesLang;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sortOption) {
|
||||||
|
const [field, order] = sortOption.split('_');
|
||||||
|
AppState.filteredTutors.sort((a, b) => {
|
||||||
|
const aVal = a.price_per_hour;
|
||||||
|
const bVal = b.price_per_hour;
|
||||||
|
return order === 'asc' ? aVal - bVal : bVal - aVal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AppState.currentTutorPage = 1;
|
||||||
|
renderTutors();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTutors() {
|
||||||
|
const tbody = document.getElementById('tutorsList');
|
||||||
|
if (!tbody) return;
|
||||||
|
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
const startIdx = (AppState.currentTutorPage - 1) *
|
||||||
|
AppState.tutorsPerPage;
|
||||||
|
const endIdx = startIdx + AppState.tutorsPerPage;
|
||||||
|
const tutorsToShow = AppState.filteredTutors
|
||||||
|
.slice(startIdx, endIdx);
|
||||||
|
|
||||||
|
tutorsToShow.forEach(tutor => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.dataset.tutorId = tutor.id;
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${tutor.name}</td>
|
||||||
|
<td><span class="badge level-badge
|
||||||
|
${getLevelBadgeClass(tutor.language_level)}">
|
||||||
|
${getLevelText(tutor.language_level)}
|
||||||
|
</span></td>
|
||||||
|
<td>${tutor.languages_offered.join(', ')}</td>
|
||||||
|
<td>${tutor.work_experience} лет</td>
|
||||||
|
<td>${formatPrice(tutor.price_per_hour)}/час</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-primary select-tutor-btn">
|
||||||
|
Выбрать
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
tr.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('.select-tutor-btn')) {
|
||||||
|
selectTutor(tutor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectBtn = tr.querySelector('.select-tutor-btn');
|
||||||
|
selectBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
selectTutor(tutor);
|
||||||
|
});
|
||||||
|
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderTutorPagination();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTutorPagination() {
|
||||||
|
const container = document.getElementById('tutorsPagination');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(AppState.filteredTutors.length /
|
||||||
|
AppState.tutorsPerPage);
|
||||||
|
|
||||||
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
|
const createPageItem = (page, text, disabled = false) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = `page-item ${disabled ? 'disabled' : ''}
|
||||||
|
${page === AppState.currentTutorPage ?
|
||||||
|
'active' : ''}`;
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.className = 'page-link';
|
||||||
|
a.href = '#tutors';
|
||||||
|
a.textContent = text || page;
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
a.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
AppState.currentTutorPage = page;
|
||||||
|
renderTutors();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
li.appendChild(a);
|
||||||
|
return li;
|
||||||
|
};
|
||||||
|
|
||||||
|
container.appendChild(
|
||||||
|
createPageItem(AppState.currentTutorPage - 1, 'Назад',
|
||||||
|
AppState.currentTutorPage === 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
if (i === 1 || i === totalPages ||
|
||||||
|
(i >= AppState.currentTutorPage - 1 &&
|
||||||
|
i <= AppState.currentTutorPage + 1)) {
|
||||||
|
container.appendChild(createPageItem(i, i, false));
|
||||||
|
} else if (i === AppState.currentTutorPage - 2 ||
|
||||||
|
i === AppState.currentTutorPage + 2) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'page-item disabled';
|
||||||
|
li.innerHTML = '<span class="page-link">...</span>';
|
||||||
|
container.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(
|
||||||
|
createPageItem(AppState.currentTutorPage + 1, 'Вперед',
|
||||||
|
AppState.currentTutorPage === totalPages)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выбрать репетитора и открыть форму заказа
|
||||||
|
function selectTutor(tutor) {
|
||||||
|
document.querySelectorAll('#tutorsList tr').forEach(row => {
|
||||||
|
row.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedRow = document.querySelector(
|
||||||
|
`[data-tutor-id="${tutor.id}"]`
|
||||||
|
);
|
||||||
|
if (selectedRow) {
|
||||||
|
selectedRow.classList.add('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
AppState.selectedTutor = tutor;
|
||||||
|
openTutorModal(tutor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Открыть модальное окно заказа репетитора
|
||||||
|
function openTutorModal(tutor) {
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('tutorModal')
|
||||||
|
);
|
||||||
|
|
||||||
|
document.getElementById('tutorId').value = tutor.id;
|
||||||
|
document.getElementById('tutorName').value = tutor.name;
|
||||||
|
|
||||||
|
['tutorStudentName', 'tutorStudentPhone',
|
||||||
|
'tutorStudentEmail', 'tutorStudentMessage'].forEach(id => {
|
||||||
|
document.getElementById(id).value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправить форму заказа репетитора на сервер
|
||||||
|
async function submitTutorOrderForm() {
|
||||||
|
const form = document.getElementById('tutorOrderForm');
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tutorId = parseInt(document.getElementById('tutorId').value);
|
||||||
|
const tutor = AppState.selectedTutor;
|
||||||
|
|
||||||
|
// Используем текущую дату как плейсхолдер
|
||||||
|
const today = new Date();
|
||||||
|
const dateStr = today.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
const orderData = {
|
||||||
|
tutor_id: tutorId,
|
||||||
|
course_id: 0,
|
||||||
|
date_start: dateStr,
|
||||||
|
time_start: '10:00',
|
||||||
|
duration: 1,
|
||||||
|
persons: 1,
|
||||||
|
price: tutor ? tutor.price_per_hour : 0,
|
||||||
|
early_registration: false,
|
||||||
|
group_enrollment: false,
|
||||||
|
intensive_course: false,
|
||||||
|
supplementary: false,
|
||||||
|
personalized: false,
|
||||||
|
excursions: false,
|
||||||
|
assessment: false,
|
||||||
|
interactive: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const contactData = {
|
||||||
|
name: document.getElementById('tutorStudentName').value,
|
||||||
|
phone: document.getElementById('tutorStudentPhone').value,
|
||||||
|
email: document.getElementById('tutorStudentEmail').value,
|
||||||
|
message: document.getElementById('tutorStudentMessage').value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createOrder(orderData, contactData);
|
||||||
|
const modal = bootstrap.Modal.getInstance(
|
||||||
|
document.getElementById('tutorModal')
|
||||||
|
);
|
||||||
|
modal.hide();
|
||||||
|
form.reset();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating order:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue