This commit is contained in:
@@ -26,7 +26,7 @@ const translations = {
|
||||
'select.alphabetical': 'По алфавиту',
|
||||
|
||||
// Review Form
|
||||
'form.select_country': 'Выберите страну',
|
||||
'form.select_country': 'Выберите страну (необязательно)',
|
||||
'form.select_city': 'Город (необязательно)',
|
||||
'form.another_country': 'Другая страна',
|
||||
'form.country_placeholder': 'Введите название страны',
|
||||
@@ -355,7 +355,7 @@ const translations = {
|
||||
'select.alphabetical': 'Alphabetical',
|
||||
|
||||
// Review Form
|
||||
'form.select_country': 'Select country',
|
||||
'form.select_country': 'Select country (optional)',
|
||||
'form.select_city': 'City (optional)',
|
||||
'form.another_country': 'Other country',
|
||||
'form.country_placeholder': 'Enter country name',
|
||||
@@ -684,7 +684,7 @@ const translations = {
|
||||
'select.alphabetical': 'Алфавит ала',
|
||||
|
||||
// Review Form
|
||||
'form.select_country': 'Атәыла алхразы',
|
||||
'form.select_country': 'Атәыла алхразы (иахәҭахым)',
|
||||
'form.select_city': 'Ақалақь (иахәҭахым)',
|
||||
'form.another_country': 'Атәыла даҽаны',
|
||||
'form.country_placeholder': 'Атәыла ахьӡ аҭазыргыла',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const COUNTRIES = [
|
||||
{ code: 'AF', name: 'Afghanistan', nameRu: 'Афганистан' },
|
||||
{ code: 'AB', name: 'Abkhazia', nameRu: 'Абхазия' },
|
||||
{ code: 'AL', name: 'Albania', nameRu: 'Албания' },
|
||||
{ code: 'DZ', name: 'Algeria', nameRu: 'Алжир' },
|
||||
{ code: 'AD', name: 'Andorra', nameRu: 'Андорра' },
|
||||
|
||||
@@ -80,14 +80,14 @@
|
||||
<div class="col-auto"><div class="stat-divider"></div></div>
|
||||
<div class="col-auto">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number"><span class="counter" data-target="3">0</span></div>
|
||||
<div class="stat-number"><span class="counter" data-stat="categories" data-target="3">0</span></div>
|
||||
<div class="stat-label" data-i18n="hero.stat_categories">категории</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto"><div class="stat-divider"></div></div>
|
||||
<div class="col-auto">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number"><span class="counter" data-target="98">0</span>%</div>
|
||||
<div class="stat-number"><span class="counter" data-stat="satisfaction" data-target="98">0</span>%</div>
|
||||
<div class="stat-label" data-i18n="hero.stat_guests">довольных гостей</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -936,7 +936,7 @@
|
||||
<label data-i18n="form.select_country">Страна</label>
|
||||
<div class="review-select-wrapper">
|
||||
<select class="review-form-control" id="reviewCountry" required>
|
||||
<option value="" data-i18n="form.select_country">Выберите страну</option>
|
||||
<option value="" data-i18n="form.select_country">Выберите страну (необязательно)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="review-error" id="countryError" data-i18n="validation.country_required">Выберите страну</div>
|
||||
|
||||
@@ -37,9 +37,22 @@ document.querySelectorAll('.animate-on-scroll').forEach(el => {
|
||||
});
|
||||
|
||||
// Counter animation
|
||||
const counterObserver = new IntersectionObserver((entries) => {
|
||||
const statsPromise = fetch('/api/stats').then(r => r.ok ? r.json() : null).catch(() => null);
|
||||
|
||||
const counterObserver = new IntersectionObserver(async (entries) => {
|
||||
const stats = await statsPromise;
|
||||
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
if (stats) {
|
||||
entry.target.querySelectorAll('.counter[data-stat]').forEach(el => {
|
||||
const stat = el.getAttribute('data-stat');
|
||||
if (stats[stat] !== undefined) {
|
||||
el.setAttribute('data-target', stats[stat]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const counters = entry.target.querySelectorAll('.counter');
|
||||
counters.forEach(counter => {
|
||||
const target = parseInt(counter.getAttribute('data-target'));
|
||||
|
||||
@@ -140,13 +140,24 @@ function populateCountrySelect() {
|
||||
const select = document.getElementById('reviewCountry');
|
||||
if (!select) return;
|
||||
|
||||
const PINNED_CODES = ['RU', 'AB'];
|
||||
|
||||
select.innerHTML = '<option value="">' + I18n.t('form.select_country') + '</option>';
|
||||
|
||||
const pinnedCountries = countriesCache.filter(c => PINNED_CODES.includes(c.code));
|
||||
pinnedCountries.forEach(country => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = country.code;
|
||||
opt.textContent = I18n.currentLang === 'ru' ? country.nameRu : country.name;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
const popularCodes = popularCountriesCache.map(c => c.country_code);
|
||||
const popularCountries = [];
|
||||
const otherCountries = [];
|
||||
|
||||
countriesCache.forEach(country => {
|
||||
if (PINNED_CODES.includes(country.code)) return;
|
||||
if (popularCodes.includes(country.code)) {
|
||||
popularCountries.push(country);
|
||||
} else {
|
||||
|
||||
23
server.js
23
server.js
@@ -392,6 +392,28 @@ app.get('/api/translations/:lang', (req, res) => {
|
||||
res.json(translations);
|
||||
});
|
||||
|
||||
app.get('/api/stats', (req, res) => {
|
||||
db.get(`SELECT COUNT(DISTINCT type) as count FROM rooms WHERE is_active = 1`, [], (err, row) => {
|
||||
if (err) {
|
||||
console.error('Stats categories error:', err);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
const categories = (row && row.count > 0) ? row.count : Object.keys(config.ROOM_TYPES).length;
|
||||
|
||||
db.get(`SELECT COUNT(*) as total, SUM(CASE WHEN stars >= 4 THEN 1 ELSE 0 END) as satisfied FROM reviews WHERE is_approved = 1`, [], (err2, row2) => {
|
||||
if (err2) {
|
||||
console.error('Stats reviews error:', err2);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
let satisfaction = 100;
|
||||
if (row2 && row2.total > 0) {
|
||||
satisfaction = Math.round((row2.satisfied / row2.total) * 100);
|
||||
}
|
||||
res.json({ categories, satisfaction });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/countries-cities', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'data', 'countries-cities.js'));
|
||||
});
|
||||
@@ -404,6 +426,7 @@ app.get('/api/countries', (req, res) => {
|
||||
if (match) {
|
||||
try {
|
||||
let jsonStr = match[1];
|
||||
jsonStr = jsonStr.replace(/(\{|\,)\s*(\w+):/g, '$1"$2":');
|
||||
jsonStr = jsonStr.replace(/'/g, '"');
|
||||
const countries = JSON.parse(jsonStr);
|
||||
res.json(countries);
|
||||
|
||||
Reference in New Issue
Block a user