Deploy Bot commited on
Commit
06e2bc3
·
1 Parent(s): 1e66a57

Feat: Add Product Condition (New vs Used)

Browse files
src/controllers/userController.js CHANGED
@@ -189,21 +189,39 @@ exports.showSubCategories = async (ctx, parentId) => {
189
  }
190
  };
191
 
192
- // Show Products in Category (Redirect to Carousel)
193
  exports.showProducts = async (ctx, catId) => {
194
- // Start browsing at index 0
195
- return exports.browseCategory(ctx, catId, 0, 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  };
197
 
198
  // Browse Category (Carousel Logic)
199
- exports.browseCategory = async (ctx, catId, prodIndex, mediaIndex = 0) => {
200
  try {
201
  const id = catId.replace('cat_', '');
202
  const category = await Category.findOne({ id: id });
203
  if (!category) return ctx.reply("Kategoriya topilmadi.");
204
 
205
- const products = await Product.find({ category: category.name });
206
- if (!products || products.length === 0) return ctx.reply(ctx.i18n.no_cats); // Or specific no_products key
 
 
 
207
 
208
  // Handle Product Index Bounds
209
  let page = parseInt(prodIndex);
@@ -238,15 +256,16 @@ exports.browseCategory = async (ctx, catId, prodIndex, mediaIndex = 0) => {
238
 
239
  // Buttons
240
  // Navigation: prev/next product. Media index resets to 0.
 
241
  let navigationRow = [
242
- Markup.button.callback("⬅️", `br_${id}_${page - 1}_0`),
243
  Markup.button.callback(`${page + 1} / ${products.length}`, "noop"),
244
- Markup.button.callback("➡️", `br_${id}_${page + 1}_0`)
245
  ];
246
 
247
  let mediaRow = [];
248
  if (product.media && product.media.length > 1) {
249
- mediaRow.push(Markup.button.callback(`📸 ${mPage + 1}/${product.media.length}`, `br_${id}_${page}_${mPage + 1}`)); // Logic: Next media
250
  }
251
 
252
  let actionRow = [];
 
189
  }
190
  };
191
 
192
+ // Show Products (Ask for Condition first)
193
  exports.showProducts = async (ctx, catId) => {
194
+ const id = catId.replace('cat_', '');
195
+ const buttons = [
196
+ [
197
+ Markup.button.callback("🆕 Yangi", `cond_${id}_new`),
198
+ Markup.button.callback("♻️ B/U", `cond_${id}_used`)
199
+ ],
200
+ [Markup.button.callback("🌐 Barchasi", `cond_${id}_all`)],
201
+ [Markup.button.callback(ctx.i18n.btn_back_nav || "⬅️ Orqaga", "back_to_cats")]
202
+ ];
203
+ // Try edit, fallback to reply
204
+ await ctx.editMessageText("🔎 Qaysi turdagi mahsulotlarni ko'rmoqchisiz?", Markup.inlineKeyboard(buttons))
205
+ .catch(() => ctx.reply("🔎 Qaysi turdagi mahsulotlarni ko'rmoqchisiz?", Markup.inlineKeyboard(buttons)));
206
+ };
207
+
208
+ // Filter Handler
209
+ exports.filterByCondition = async (ctx, catId, condition) => {
210
+ return exports.browseCategory(ctx, catId, 0, 0, condition);
211
  };
212
 
213
  // Browse Category (Carousel Logic)
214
+ exports.browseCategory = async (ctx, catId, prodIndex, mediaIndex = 0, conditionFilter = 'all') => {
215
  try {
216
  const id = catId.replace('cat_', '');
217
  const category = await Category.findOne({ id: id });
218
  if (!category) return ctx.reply("Kategoriya topilmadi.");
219
 
220
+ let query = { category: category.name };
221
+ if (conditionFilter !== 'all') query.condition = conditionFilter;
222
+
223
+ const products = await Product.find(query);
224
+ if (!products || products.length === 0) return ctx.reply("❌ Bu kategoriyada mahsulotlar yo'q.");
225
 
226
  // Handle Product Index Bounds
227
  let page = parseInt(prodIndex);
 
256
 
257
  // Buttons
258
  // Navigation: prev/next product. Media index resets to 0.
259
+ // Format: br_catId_prodIndex_mediaIndex_condition
260
  let navigationRow = [
261
+ Markup.button.callback("⬅️", `br_${id}_${page - 1}_0_${conditionFilter}`),
262
  Markup.button.callback(`${page + 1} / ${products.length}`, "noop"),
263
+ Markup.button.callback("➡️", `br_${id}_${page + 1}_0_${conditionFilter}`)
264
  ];
265
 
266
  let mediaRow = [];
267
  if (product.media && product.media.length > 1) {
268
+ mediaRow.push(Markup.button.callback(`📸 ${mPage + 1}/${product.media.length}`, `br_${id}_${page}_${mPage + 1}_${conditionFilter}`));
269
  }
270
 
271
  let actionRow = [];
src/main.js CHANGED
@@ -450,13 +450,29 @@ bot.action(/cat_(.+)/, async (ctx) => {
450
  });
451
 
452
  // Carousel Navigation
453
- // Carousel Navigation (New)
454
- bot.action(/br_(.+)_(.+)_(.+)/, (ctx) => {
455
- // pattern: br_catId_prodIndex_mediaIndex
456
  const catId = "cat_" + ctx.match[1];
457
- const prodIndex = parseInt(ctx.match[2]);
458
- const mediaIndex = parseInt(ctx.match[3]);
459
- userController.browseCategory(ctx, catId, prodIndex, mediaIndex);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  });
461
 
462
  // Backward compatibility or redirect
 
450
  });
451
 
452
  // Carousel Navigation
453
+ // Condition Filter Selection
454
+ bot.action(/cond_(.+)_(.+)/, (ctx) => {
 
455
  const catId = "cat_" + ctx.match[1];
456
+ const condition = ctx.match[2];
457
+ userController.filterByCondition(ctx, catId, condition);
458
+ });
459
+
460
+ // Carousel Navigation (New)
461
+ bot.action(/br_(.+)/, (ctx) => {
462
+ // pattern: br_catId_prodIndex_mediaIndex_condition
463
+ // We split manually because condition is optional and captured groups are tricky
464
+ const text = ctx.match[1];
465
+ const parts = text.split('_');
466
+
467
+ // Safety check
468
+ if (parts.length < 3) return ctx.answerCbQuery("Error");
469
+
470
+ const catId = "cat_" + parts[0];
471
+ const prodIndex = parseInt(parts[1]);
472
+ const mediaIndex = parseInt(parts[2]);
473
+ const condition = parts[3] || 'all'; // Default to all if missing
474
+
475
+ userController.browseCategory(ctx, catId, prodIndex, mediaIndex, condition);
476
  });
477
 
478
  // Backward compatibility or redirect
src/models/Product.js CHANGED
@@ -7,6 +7,7 @@ const productSchema = new mongoose.Schema({
7
  originalPrice: { type: Number, default: null }, // For Flash Sales
8
  discountPercent: { type: Number, default: 0 }, // Discount %
9
  quantity: { type: Number, default: 0 },
 
10
  description: String,
11
  category: String, // Category ID
12
  media: [{
 
7
  originalPrice: { type: Number, default: null }, // For Flash Sales
8
  discountPercent: { type: Number, default: 0 }, // Discount %
9
  quantity: { type: Number, default: 0 },
10
+ condition: { type: String, enum: ['new', 'used'], default: 'new' },
11
  description: String,
12
  category: String, // Category ID
13
  media: [{
src/scenes/admin/addProduct.js CHANGED
@@ -64,10 +64,41 @@ const addProductScene = new Scenes.WizardScene(
64
  if (!ctx.message || !ctx.message.text || isNaN(ctx.message.text)) return ctx.reply(i18n.admin.error_num);
65
  ctx.wizard.state.product.quantity = parseInt(ctx.message.text);
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  ctx.reply(i18n.admin.enter_desc, Markup.keyboard([[i18n.btn_cancel]]).resize());
68
  return ctx.wizard.next();
69
  },
70
- // Step 5: Ask Category
71
  async (ctx) => {
72
  const i18n = ctx.i18n;
73
  if (ctx.message && ctx.message.text === i18n.btn_cancel) {
 
64
  if (!ctx.message || !ctx.message.text || isNaN(ctx.message.text)) return ctx.reply(i18n.admin.error_num);
65
  ctx.wizard.state.product.quantity = parseInt(ctx.message.text);
66
 
67
+ // ASK CONDITION
68
+ ctx.reply("Maxsulot xolati qanday?", Markup.keyboard([
69
+ ["🆕 Yangi", "♻️ B/U"],
70
+ [i18n.btn_cancel]
71
+ ]).resize());
72
+ return ctx.wizard.next();
73
+ },
74
+ // Step 5: Handle Condition & Ask Description
75
+ (ctx) => {
76
+ const i18n = ctx.i18n;
77
+ const text = ctx.message.text;
78
+ if (text === i18n.btn_cancel) {
79
+ ctx.scene.leave();
80
+ userController.start(ctx, i18n.cancel_process);
81
+ return;
82
+ }
83
+
84
+ if (text === "🆕 Yangi") {
85
+ ctx.wizard.state.product.condition = 'new';
86
+ } else if (text === "♻️ B/U") {
87
+ ctx.wizard.state.product.condition = 'used';
88
+ } else {
89
+ // Default or Retry? Let's assume buttons are clicked.
90
+ // If manual type, maybe fallback to 'new' or ask again.
91
+ // Stricter:
92
+ if (text !== "🆕 Yangi" && text !== "♻️ B/U") {
93
+ return ctx.reply("Iltimos tugmani tanlang: Yangi yoki B/U");
94
+ }
95
+ ctx.wizard.state.product.condition = text === "🆕 Yangi" ? 'new' : 'used';
96
+ }
97
+
98
  ctx.reply(i18n.admin.enter_desc, Markup.keyboard([[i18n.btn_cancel]]).resize());
99
  return ctx.wizard.next();
100
  },
101
+ // Step 6: Ask Category (Shifted)
102
  async (ctx) => {
103
  const i18n = ctx.i18n;
104
  if (ctx.message && ctx.message.text === i18n.btn_cancel) {