fix: location navigation uses IDs and pipe separators instead of underscore
Critical fix for product management location selection:
- Country/city callback_data now uses pipe | as separator with
encodeURIComponent/decodeURIComponent for special chars
- District selection uses location ID (prod_loc_{id}, shop_loc_{id})
instead of underscore-delimited country_city_district text
- Empty district names now show city name as fallback
- LocationService.getLocationsByCountryAndCity() returns id+district
for building callback_data with location IDs
- All error handlers in admin product navigation use editOrSendCallback
to avoid chat clutter
- Routes updated: prod_district_ → prod_loc_, shop_district_ → shop_loc_
This fixes the bug where selecting country/city/district in admin panel
or shop failed because split('_') broke on multi-word names or empty
district values.
This commit is contained in:
@@ -4,6 +4,7 @@ import bot from '../../../context/bot.js';
|
||||
import CategoryService from '../../../services/categoryService.js';
|
||||
import ProductService from '../../../services/productService.js';
|
||||
import logger from '../../../utils/logger.js';
|
||||
import { editOrSendCallback } from '../../../utils/messageUtils.js';
|
||||
|
||||
export default class CategorySelectionHandler {
|
||||
|
||||
@@ -19,9 +20,9 @@ export default class CategorySelectionHandler {
|
||||
try {
|
||||
const category = await CategoryService.getCategoryById(categoryId);
|
||||
const location = await LocationService.getLocationById(locationId);
|
||||
|
||||
|
||||
const products = await ProductService.getProductsByCategoryId(categoryId);
|
||||
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
...products.map(prod => [{
|
||||
@@ -29,7 +30,7 @@ export default class CategorySelectionHandler {
|
||||
callback_data: `view_product_${prod.id}`
|
||||
}]),
|
||||
[{ text: '➕ Add Product', callback_data: `add_product_${locationId}_${categoryId}` }],
|
||||
[{ text: '« Back', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }]
|
||||
[{ text: '« Back', callback_data: `prod_loc_${locationId}` }]
|
||||
]
|
||||
};
|
||||
|
||||
@@ -43,7 +44,7 @@ export default class CategorySelectionHandler {
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error in handleCategorySelection');
|
||||
await bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
||||
await editOrSendCallback(callbackQuery, 'Error loading products. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import CategoryService from '../../../services/categoryService.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
import userStates from '../../../context/userStates.js';
|
||||
import logger from '../../../utils/logger.js';
|
||||
import { editOrSendCallback } from '../../../utils/messageUtils.js';
|
||||
|
||||
export default class DistrictHandler {
|
||||
|
||||
@@ -14,18 +15,19 @@ export default class DistrictHandler {
|
||||
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
const [country, city] = callbackQuery.data.replace('prod_city_', '').split('_');
|
||||
const payload = callbackQuery.data.replace('prod_city_', '');
|
||||
const [country, city] = payload.split('|').map(decodeURIComponent);
|
||||
|
||||
try {
|
||||
const districts = await LocationService.getDistrictsByCountryAndCity(country, city);
|
||||
const locations = await LocationService.getLocationsByCountryAndCity(country, city);
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
...districts.map(loc => [{
|
||||
text: loc.district,
|
||||
callback_data: `prod_district_${country}_${city}_${loc.district}`
|
||||
...locations.map(loc => [{
|
||||
text: loc.district || loc.city,
|
||||
callback_data: `prod_loc_${loc.id}`
|
||||
}]),
|
||||
[{text: '« Back', callback_data: `prod_country_${country}`}]
|
||||
[{text: '« Back', callback_data: `prod_country_${encodeURIComponent(country)}`}]
|
||||
]
|
||||
};
|
||||
|
||||
@@ -39,7 +41,7 @@ export default class DistrictHandler {
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error in handleCitySelection');
|
||||
await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
||||
await editOrSendCallback(callbackQuery, 'Error loading districts. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +52,12 @@ export default class DistrictHandler {
|
||||
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
const [country, city, district] = callbackQuery.data.replace('prod_district_', '').split('_');
|
||||
const locationId = parseInt(callbackQuery.data.replace('prod_loc_', ''), 10);
|
||||
|
||||
await userStates.delete(chatId);
|
||||
|
||||
try {
|
||||
const location = await LocationService.getLocation(country, city, district);
|
||||
const location = await LocationService.getLocationById(locationId);
|
||||
|
||||
if (!location) {
|
||||
throw new Error('Location not found');
|
||||
@@ -70,7 +72,7 @@ export default class DistrictHandler {
|
||||
callback_data: `prod_category_${location.id}_${cat.id}`
|
||||
}]),
|
||||
[{text: '➕ Add Category', callback_data: `add_category_${location.id}`}],
|
||||
[{text: '« Back', callback_data: `prod_city_${country}_${city}`}]
|
||||
[{text: '« Back', callback_data: `prod_city_${encodeURIComponent(location.country)}|${encodeURIComponent(location.city)}`}]
|
||||
]
|
||||
};
|
||||
|
||||
@@ -84,7 +86,7 @@ export default class DistrictHandler {
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error in handleDistrictSelection');
|
||||
await bot.sendMessage(chatId, 'Error loading categories. Please try again.');
|
||||
await editOrSendCallback(callbackQuery, 'Error loading categories. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { isAdmin } from '../../../middleware/auth.js';
|
||||
import LocationService from '../../../services/locationService.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
import logger from '../../../utils/logger.js';
|
||||
import { editOrSendCallback } from '../../../utils/messageUtils.js';
|
||||
|
||||
export default class NavigationHandler {
|
||||
|
||||
@@ -14,7 +15,7 @@ export default class NavigationHandler {
|
||||
}
|
||||
|
||||
try {
|
||||
const countries = await LocationService.getCountries()
|
||||
const countries = await LocationService.getCountries();
|
||||
|
||||
if (countries.length === 0) {
|
||||
await bot.sendMessage(
|
||||
@@ -34,7 +35,7 @@ export default class NavigationHandler {
|
||||
const keyboard = {
|
||||
inline_keyboard: countries.map(loc => [{
|
||||
text: loc.country,
|
||||
callback_data: `prod_country_${loc.country}`
|
||||
callback_data: `prod_country_${encodeURIComponent(loc.country)}`
|
||||
}])
|
||||
};
|
||||
|
||||
@@ -56,16 +57,16 @@ export default class NavigationHandler {
|
||||
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
const country = callbackQuery.data.replace('prod_country_', '');
|
||||
const country = decodeURIComponent(callbackQuery.data.replace('prod_country_', ''));
|
||||
|
||||
try {
|
||||
const cities = await LocationService.getCitiesByCountry(country)
|
||||
const cities = await LocationService.getCitiesByCountry(country);
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
...cities.map(loc => [{
|
||||
text: loc.city,
|
||||
callback_data: `prod_city_${country}_${loc.city}`
|
||||
callback_data: `prod_city_${encodeURIComponent(country)}|${encodeURIComponent(loc.city)}`
|
||||
}]),
|
||||
[{text: '« Back', callback_data: 'manage_products'}]
|
||||
]
|
||||
@@ -81,7 +82,7 @@ export default class NavigationHandler {
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error in handleCountrySelection');
|
||||
await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
||||
await editOrSendCallback(callbackQuery, 'Error loading cities. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export default class UserProductHandler {
|
||||
const keyboard = {
|
||||
inline_keyboard: countries.map(loc => [{
|
||||
text: loc.country,
|
||||
callback_data: `shop_country_${loc.country}`
|
||||
callback_data: `shop_country_${encodeURIComponent(loc.country)}`
|
||||
}])
|
||||
};
|
||||
|
||||
@@ -88,7 +88,7 @@ export default class UserProductHandler {
|
||||
static async handleCountrySelection(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
const country = callbackQuery.data.replace('shop_country_', '');
|
||||
const country = decodeURIComponent(callbackQuery.data.replace('shop_country_', ''));
|
||||
|
||||
try {
|
||||
const cities = await LocationService.getCitiesByCountry(country);
|
||||
@@ -97,7 +97,7 @@ export default class UserProductHandler {
|
||||
inline_keyboard: [
|
||||
...cities.map(loc => [{
|
||||
text: loc.city,
|
||||
callback_data: `shop_city_${country}_${loc.city}`
|
||||
callback_data: `shop_city_${encodeURIComponent(country)}|${encodeURIComponent(loc.city)}`
|
||||
}]),
|
||||
[{text: '« Back to Countries', callback_data: 'shop_start'}]
|
||||
]
|
||||
@@ -113,25 +113,26 @@ export default class UserProductHandler {
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error in handleCountrySelection');
|
||||
await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
||||
await editOrSendCallback(callbackQuery, 'Error loading cities. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleCitySelection(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
const [country, city] = callbackQuery.data.replace('shop_city_', '').split('_');
|
||||
const payload = callbackQuery.data.replace('shop_city_', '');
|
||||
const [country, city] = payload.split('|').map(decodeURIComponent);
|
||||
|
||||
try {
|
||||
const districts = await LocationService.getDistrictsByCountryAndCity(country, city)
|
||||
const locations = await LocationService.getLocationsByCountryAndCity(country, city);
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
...districts.map(loc => [{
|
||||
text: loc.district,
|
||||
callback_data: `shop_district_${country}_${city}_${loc.district}`
|
||||
...locations.map(loc => [{
|
||||
text: loc.district || loc.city,
|
||||
callback_data: `shop_loc_${loc.id}`
|
||||
}]),
|
||||
[{text: '« Back to Cities', callback_data: `shop_country_${country}`}]
|
||||
[{text: '« Back to Cities', callback_data: `shop_country_${encodeURIComponent(country)}`}]
|
||||
]
|
||||
};
|
||||
|
||||
@@ -145,21 +146,19 @@ export default class UserProductHandler {
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error in handleCitySelection');
|
||||
await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
||||
await editOrSendCallback(callbackQuery, 'Error loading districts. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleDistrictSelection(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
const [country, city, district] = callbackQuery.data.replace('shop_district_', '').split('_');
|
||||
const locationId = parseInt(callbackQuery.data.replace('shop_loc_', ''), 10);
|
||||
|
||||
try {
|
||||
// Получаем информацию о локации
|
||||
const location = await LocationService.getLocation(country, city, district);
|
||||
const location = await LocationService.getLocationById(locationId);
|
||||
|
||||
if (!location) {
|
||||
// Если локация не найдена, вернуть пользователя к предыдущему шагу
|
||||
await bot.editMessageText(
|
||||
'Location not found. Returning to previous menu.',
|
||||
{
|
||||
@@ -167,7 +166,7 @@ export default class UserProductHandler {
|
||||
message_id: messageId,
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '« Back', callback_data: `shop_city_${country}_${city}` }
|
||||
{ text: '« Back', callback_data: `shop_city_${encodeURIComponent(location?.country || '')}|${encodeURIComponent(location?.city || '')}` }
|
||||
]]
|
||||
}
|
||||
}
|
||||
@@ -175,12 +174,11 @@ export default class UserProductHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Сохраняем текстовое представление локации в состоянии пользователя
|
||||
await userStates.set(chatId, {
|
||||
location: `${country}_${city}_${district}`
|
||||
location: `${location.country}_${location.city}_${location.district}`,
|
||||
locationId: location.id
|
||||
});
|
||||
|
||||
// Получаем категории для выбранной локации
|
||||
const categories = await CategoryService.getCategoriesByLocationId(location.id);
|
||||
|
||||
const keyboard = {
|
||||
@@ -189,7 +187,7 @@ export default class UserProductHandler {
|
||||
text: cat.name,
|
||||
callback_data: `shop_category_${location.id}_${cat.id}`
|
||||
}]),
|
||||
[{ text: '« Back', callback_data: `shop_city_${country}_${city}` }]
|
||||
[{ text: '« Back', callback_data: `shop_city_${encodeURIComponent(location.country)}|${encodeURIComponent(location.city)}` }]
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ export function registerRoutes() {
|
||||
logDebug(cq.data, 'handleCitySelection');
|
||||
await userProductHandler.handleCitySelection(cq);
|
||||
});
|
||||
callbackRouter.registerPrefix('shop_district_', async (cq) => {
|
||||
callbackRouter.registerPrefix('shop_loc_', async (cq) => {
|
||||
logDebug(cq.data, 'handleDistrictSelection');
|
||||
await userProductHandler.handleDistrictSelection(cq);
|
||||
});
|
||||
@@ -233,7 +233,7 @@ export function registerRoutes() {
|
||||
logDebug(cq.data, 'handleCitySelection');
|
||||
await productHandler.handleCitySelection(cq);
|
||||
});
|
||||
callbackRouter.registerPrefix('prod_district_', async (cq) => {
|
||||
callbackRouter.registerPrefix('prod_loc_', async (cq) => {
|
||||
logDebug(cq.data, 'handleDistrictSelection');
|
||||
await productHandler.handleDistrictSelection(cq);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,14 @@ class LocationService {
|
||||
|
||||
static async getDistrictsByCountryAndCity(country, city) {
|
||||
return await db.allAsync(
|
||||
'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
||||
'SELECT id, district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
||||
[country, city]
|
||||
);
|
||||
}
|
||||
|
||||
static async getLocationsByCountryAndCity(country, city) {
|
||||
return await db.allAsync(
|
||||
'SELECT id, country, city, district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
||||
[country, city]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user