products pagination

This commit is contained in:
Artyom Ashirov
2024-11-15 08:19:54 +03:00
parent df3149e59a
commit e1eda05afe
2 changed files with 602 additions and 508 deletions

View File

@@ -32,7 +32,7 @@ export default class AdminProductHandler {
{ {
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '📍 Manage Locations', callback_data: 'view_locations' } {text: '📍 Manage Locations', callback_data: 'view_locations'}
]] ]]
} }
} }
@@ -50,7 +50,7 @@ export default class AdminProductHandler {
await this.bot.sendMessage( await this.bot.sendMessage(
chatId, chatId,
'🌍 Select country to manage products:', '🌍 Select country to manage products:',
{ reply_markup: keyboard } {reply_markup: keyboard}
); );
} catch (error) { } catch (error) {
console.error('Error in handleProductManagement:', error); console.error('Error in handleProductManagement:', error);
@@ -75,7 +75,7 @@ export default class AdminProductHandler {
text: loc.city, text: loc.city,
callback_data: `prod_city_${country}_${loc.city}` callback_data: `prod_city_${country}_${loc.city}`
}]), }]),
[{ text: '« Back', callback_data: 'manage_products' }] [{text: '« Back', callback_data: 'manage_products'}]
] ]
}; };
@@ -110,7 +110,7 @@ export default class AdminProductHandler {
text: loc.district, text: loc.district,
callback_data: `prod_district_${country}_${city}_${loc.district}` callback_data: `prod_district_${country}_${city}_${loc.district}`
}]), }]),
[{ text: '« Back', callback_data: `prod_country_${country}` }] [{text: '« Back', callback_data: `prod_country_${country}`}]
] ]
}; };
@@ -154,8 +154,8 @@ export default class AdminProductHandler {
text: cat.name, text: cat.name,
callback_data: `prod_category_${location.id}_${cat.id}` callback_data: `prod_category_${location.id}_${cat.id}`
}]), }]),
[{ text: ' Add Category', callback_data: `add_category_${location.id}` }], [{text: ' Add Category', callback_data: `add_category_${location.id}`}],
[{ text: '« Back', callback_data: `prod_city_${country}_${city}` }] [{text: '« Back', callback_data: `prod_city_${country}_${city}`}]
] ]
}; };
@@ -198,7 +198,10 @@ export default class AdminProductHandler {
{ {
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '« Back to Categories', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` } {
text: '« Back to Categories',
callback_data: `prod_district_${location.country}_${location.city}_${location.district}`
}
]] ]]
} }
} }
@@ -221,7 +224,7 @@ export default class AdminProductHandler {
const chatId = callbackQuery.message.chat.id; const chatId = callbackQuery.message.chat.id;
const locationId = callbackQuery.data.replace('add_category_', ''); const locationId = callbackQuery.data.replace('add_category_', '');
this.userStates.set(chatId, { action: `add_category_${locationId}` }); this.userStates.set(chatId, {action: `add_category_${locationId}`});
await this.bot.editMessageText( await this.bot.editMessageText(
'Please enter the name for the new category:', 'Please enter the name for the new category:',
@@ -230,7 +233,7 @@ export default class AdminProductHandler {
message_id: callbackQuery.message.message_id, message_id: callbackQuery.message.message_id,
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '❌ Cancel', callback_data: `prod_district_${locationId}` } {text: '❌ Cancel', callback_data: `prod_district_${locationId}`}
]] ]]
} }
} }
@@ -257,9 +260,12 @@ export default class AdminProductHandler {
text: sub.name, text: sub.name,
callback_data: `prod_subcategory_${locationId}_${categoryId}_${sub.id}` callback_data: `prod_subcategory_${locationId}_${categoryId}_${sub.id}`
}]), }]),
[{ text: ' Add Subcategory', callback_data: `add_subcategory_${locationId}_${categoryId}` }], [{text: ' Add Subcategory', callback_data: `add_subcategory_${locationId}_${categoryId}`}],
[{ text: '✏️ Edit Category', callback_data: `edit_category_${locationId}_${categoryId}` }], [{text: '✏️ Edit Category', callback_data: `edit_category_${locationId}_${categoryId}`}],
[{ text: '« Back', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }] [{
text: '« Back',
callback_data: `prod_district_${location.country}_${location.city}_${location.district}`
}]
] ]
}; };
@@ -297,7 +303,10 @@ export default class AdminProductHandler {
{ {
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '« Back to Subcategories', callback_data: `prod_category_${locationId}_${categoryId}` } {
text: '« Back to Subcategories',
callback_data: `prod_category_${locationId}_${categoryId}`
}
]] ]]
} }
} }
@@ -320,7 +329,7 @@ export default class AdminProductHandler {
const chatId = callbackQuery.message.chat.id; const chatId = callbackQuery.message.chat.id;
const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_'); const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_');
this.userStates.set(chatId, { action: `add_subcategory_${locationId}_${categoryId}` }); this.userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`});
await this.bot.editMessageText( await this.bot.editMessageText(
'Please enter the name for the new subcategory:', 'Please enter the name for the new subcategory:',
@@ -329,27 +338,47 @@ export default class AdminProductHandler {
message_id: callbackQuery.message.message_id, message_id: callbackQuery.message.message_id,
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '❌ Cancel', callback_data: `prod_category_${locationId}_${categoryId}` } {text: '❌ Cancel', callback_data: `prod_category_${locationId}_${categoryId}`}
]] ]]
} }
} }
); );
} }
async handleSubcategorySelection(callbackQuery) { async viewProductsPage(locationId, categoryId, subcategoryId, page) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_');
try { try {
const limit = 10;
const offset = (page || 0) * limit;
const previousPage = page > 0 ? page - 1 : 0;
const nextPage = page + 1;
const products = await db.allAsync( const products = await db.allAsync(
`SELECT id, name, price, quantity_in_stock `SELECT id, name, price, quantity_in_stock
FROM products FROM products
WHERE location_id = ? AND category_id = ? AND subcategory_id = ? WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
ORDER BY name`, ORDER BY name
[locationId, categoryId, subcategoryId] LIMIT ?
OFFSET ?
`,
[locationId, categoryId, subcategoryId, limit, offset]
); );
if ((products.length === 0) && (page == 0)) {
return {
text: 'No products for this location',
markup: {
inline_keyboard: [
[{text: '« Back', callback_data: `prod_category_${locationId}_${categoryId}`}]
]
}
};
}
if ((products.length === 0) && (page > 0)) {
return await this.viewProductsPage(locationId, categoryId, subcategoryId, previousPage);
}
const subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]); const subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]);
const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]); const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]);
@@ -359,17 +388,48 @@ export default class AdminProductHandler {
text: `${prod.name} - $${prod.price} (${prod.quantity_in_stock} left)`, text: `${prod.name} - $${prod.price} (${prod.quantity_in_stock} left)`,
callback_data: `view_product_${prod.id}` callback_data: `view_product_${prod.id}`
}]), }]),
[{ text: '📥 Import Products', callback_data: `add_product_${locationId}_${categoryId}_${subcategoryId}` }], [{
[{ text: '« Back', callback_data: `prod_category_${locationId}_${categoryId}` }] text: '📥 Import Products',
callback_data: `add_product_${locationId}_${categoryId}_${subcategoryId}`
}],
] ]
}; };
keyboard.inline_keyboard.push([
{text: `«`, callback_data: `list_products_${locationId}_${categoryId}_${subcategoryId}_${previousPage}`},
{text: `»`, callback_data: `list_products_${locationId}_${categoryId}_${subcategoryId}_${nextPage}`},
]);
keyboard.inline_keyboard.push([
{text: '« Back', callback_data: `prod_category_${locationId}_${categoryId}`}
]);
return {
text: `📦 ${category.name} > ${subcategory.name}\nSelect product or import new ones:`,
markup: keyboard
}
} catch (error) {
console.error('Error in handleSubcategorySelection:', error);
return {text: 'Error loading products. Please try again.'};
}
}
async handleSubcategorySelection(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_');
try {
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0);
await this.bot.editMessageText( await this.bot.editMessageText(
`📦 ${category.name} > ${subcategory.name}\nSelect product or import new ones:`, text,
{ {
chat_id: chatId, chat_id: chatId,
message_id: messageId, message_id: messageId,
reply_markup: keyboard reply_markup: markup
} }
); );
} catch (error) { } catch (error) {
@@ -378,6 +438,28 @@ export default class AdminProductHandler {
} }
} }
async handleProductListPage(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
return;
}
const chatId = callbackQuery.message.chat.id;
const [locationId, categoryId, subcategoryId, page] = callbackQuery.data.replace('list_products_', '').split("_");
try {
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, parseInt(page));
await this.bot.editMessageText(text, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
reply_markup: markup,
parse_mode: 'HTML'
});
} catch (e) {
return;
}
}
async handleAddProduct(callbackQuery) { async handleAddProduct(callbackQuery) {
const chatId = callbackQuery.message.chat.id; const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id; const messageId = callbackQuery.message.message_id;
@@ -421,7 +503,10 @@ export default class AdminProductHandler {
parse_mode: 'HTML', parse_mode: 'HTML',
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '❌ Cancel', callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}` } {
text: '❌ Cancel',
callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}`
}
]] ]]
} }
}); });
@@ -498,7 +583,10 @@ export default class AdminProductHandler {
{ {
reply_markup: { reply_markup: {
inline_keyboard: [[ inline_keyboard: [[
{ text: '« Back to Products', callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}` } {
text: '« Back to Products',
callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}`
}
]] ]]
} }
} }
@@ -555,31 +643,34 @@ Coordinates: ${product.hidden_coordinates}
const keyboard = { const keyboard = {
inline_keyboard: [ inline_keyboard: [
[ [
{ text: '✏️ Edit', callback_data: `edit_product_${productId}` }, {text: '✏️ Edit', callback_data: `edit_product_${productId}`},
{ text: '❌ Delete', callback_data: `delete_product_${productId}` } {text: '❌ Delete', callback_data: `delete_product_${productId}`}
], ],
[{ text: '« Back', callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}` }] [{
text: '« Back',
callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}`
}]
] ]
}; };
// Send product photos // Send product photos
if (product.photo_url) { if (product.photo_url) {
try { try {
await this.bot.sendPhoto(chatId, product.photo_url, { caption: 'Public photo' }); await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
} catch (e) { } catch (e) {
await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Public photo' }) await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
} }
} }
if (product.hidden_photo_url) { if (product.hidden_photo_url) {
try { try {
await this.bot.sendPhoto(chatId, product.hidden_photo_url, { caption: 'Hidden photo' }); await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
} catch (e) { } catch (e) {
await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", { caption: 'Hidden photo' }) await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
} }
} }
await this.bot.deleteMessage(chatId, messageId); await this.bot.deleteMessage(chatId, messageId);
await this.bot.sendMessage(chatId, message, { reply_markup: keyboard }); await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
} catch (error) { } catch (error) {
console.error('Error in handleViewProduct:', error); console.error('Error in handleViewProduct:', error);
await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.'); await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');

View File

@@ -269,6 +269,9 @@ bot.on('callback_query', async (callbackQuery) => {
} else if (action.startsWith('prod_subcategory_')) { } else if (action.startsWith('prod_subcategory_')) {
logDebug(action, 'handleSubcategorySelection'); logDebug(action, 'handleSubcategorySelection');
await adminProductHandler.handleSubcategorySelection(callbackQuery); await adminProductHandler.handleSubcategorySelection(callbackQuery);
} else if (action.startsWith('list_products_')) {
logDebug(action, 'handleSubcategorySelection');
await adminProductHandler.handleProductListPage(callbackQuery);
} else if (action.startsWith('add_product_')) { } else if (action.startsWith('add_product_')) {
logDebug(action, 'handleAddProduct'); logDebug(action, 'handleAddProduct');
await adminProductHandler.handleAddProduct(callbackQuery); await adminProductHandler.handleAddProduct(callbackQuery);