Add product variants system and live bot activity dashboard
FEATURES IMPLEMENTED: 1. Product Multi-Buys (renamed from Variations for clarity) - Quantity-based pricing deals (e.g., 1 for £10, 3 for £25) - Renamed UI to "Multi-Buys" with tags icon for better understanding 2. Product Variants (NEW) - Support for colors, flavors, sizes, and other product options - Separate from multi-buys - these are the actual variations customers choose - Admin UI for managing variants per product - Updated OrderItem model to store selected variants as JSON array 3. Live Bot Activity Dashboard - Real-time view of customer interactions across all bots - Shows active users (last 5 minutes) - Live activity feed with user actions - Statistics including today's activities and trending products - Auto-refreshes every 5 seconds for live updates - Accessible via "Live Activity" menu item TECHNICAL CHANGES: - Modified OrderItem.SelectedVariant to SelectedVariants (JSON array) - Added BotActivityController for dashboard endpoints - Created views for variant management (ProductVariants, CreateVariant, EditVariant) - Updated Products Index to show separate buttons for Multi-Buys and Variants - Fixed duplicate DTO definitions (removed duplicate files) - Fixed ApplicationDbContext reference (changed to LittleShopContext) UI IMPROVEMENTS: - Multi-Buys: Tags icon, labeled as "pricing deals" - Variants: Palette icon, labeled as "colors/flavors" - Live dashboard with animated activity feed - Visual indicators for active users and trending products - Mobile-responsive dashboard layout This update provides the foundation for: - Customers selecting variants during checkout - Real-time monitoring of bot usage patterns - Better understanding of popular products and user behavior Next steps: Implement variant selection in TeleBot checkout flow 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
142
LittleShop/Areas/Admin/Controllers/BotActivityController.cs
Normal file
142
LittleShop/Areas/Admin/Controllers/BotActivityController.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using LittleShop.Data;
|
||||
using LittleShop.Models;
|
||||
|
||||
namespace LittleShop.Areas.Admin.Controllers;
|
||||
|
||||
[Area("Admin")]
|
||||
[Authorize(Policy = "AdminOnly")]
|
||||
public class BotActivityController : Controller
|
||||
{
|
||||
private readonly LittleShopContext _context;
|
||||
|
||||
public BotActivityController(LittleShopContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public IActionResult LiveView()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetRecentActivities(int count = 50)
|
||||
{
|
||||
var activities = await _context.BotActivities
|
||||
.Include(a => a.Bot)
|
||||
.Include(a => a.Product)
|
||||
.OrderByDescending(a => a.Timestamp)
|
||||
.Take(count)
|
||||
.Select(a => new
|
||||
{
|
||||
a.Id,
|
||||
a.UserDisplayName,
|
||||
a.ActivityType,
|
||||
a.ActivityDescription,
|
||||
a.ProductName,
|
||||
a.CategoryName,
|
||||
a.Value,
|
||||
a.Quantity,
|
||||
a.Platform,
|
||||
a.Location,
|
||||
a.Timestamp,
|
||||
BotName = a.Bot.Name,
|
||||
TimeAgo = GetTimeAgo(a.Timestamp)
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return Json(activities);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetActiveUsers()
|
||||
{
|
||||
var cutoff = DateTime.UtcNow.AddMinutes(-5); // Users active in last 5 minutes
|
||||
|
||||
var activeUsers = await _context.BotActivities
|
||||
.Where(a => a.Timestamp >= cutoff)
|
||||
.GroupBy(a => new { a.SessionIdentifier, a.UserDisplayName })
|
||||
.Select(g => new
|
||||
{
|
||||
SessionId = g.Key.SessionIdentifier,
|
||||
UserName = g.Key.UserDisplayName,
|
||||
ActivityCount = g.Count(),
|
||||
LastActivity = g.Max(a => a.Timestamp),
|
||||
LastAction = g.OrderByDescending(a => a.Timestamp).FirstOrDefault()!.ActivityDescription,
|
||||
TotalValue = g.Sum(a => a.Value ?? 0)
|
||||
})
|
||||
.OrderByDescending(u => u.LastActivity)
|
||||
.ToListAsync();
|
||||
|
||||
return Json(activeUsers);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetStatistics()
|
||||
{
|
||||
var today = DateTime.UtcNow.Date;
|
||||
var yesterday = today.AddDays(-1);
|
||||
|
||||
var stats = new
|
||||
{
|
||||
TodayActivities = await _context.BotActivities
|
||||
.Where(a => a.Timestamp >= today)
|
||||
.CountAsync(),
|
||||
|
||||
YesterdayActivities = await _context.BotActivities
|
||||
.Where(a => a.Timestamp >= yesterday && a.Timestamp < today)
|
||||
.CountAsync(),
|
||||
|
||||
UniqueUsersToday = await _context.BotActivities
|
||||
.Where(a => a.Timestamp >= today)
|
||||
.Select(a => a.SessionIdentifier)
|
||||
.Distinct()
|
||||
.CountAsync(),
|
||||
|
||||
PopularProducts = await _context.BotActivities
|
||||
.Where(a => a.ProductId != null && a.Timestamp >= today.AddDays(-7))
|
||||
.GroupBy(a => new { a.ProductId, a.ProductName })
|
||||
.Select(g => new
|
||||
{
|
||||
ProductName = g.Key.ProductName,
|
||||
ViewCount = g.Count(a => a.ActivityType == "ViewProduct"),
|
||||
AddToCartCount = g.Count(a => a.ActivityType == "AddToCart")
|
||||
})
|
||||
.OrderByDescending(p => p.ViewCount + p.AddToCartCount)
|
||||
.Take(5)
|
||||
.ToListAsync(),
|
||||
|
||||
ActivityByHour = await _context.BotActivities
|
||||
.Where(a => a.Timestamp >= today)
|
||||
.GroupBy(a => a.Timestamp.Hour)
|
||||
.Select(g => new
|
||||
{
|
||||
Hour = g.Key,
|
||||
Count = g.Count()
|
||||
})
|
||||
.OrderBy(h => h.Hour)
|
||||
.ToListAsync()
|
||||
};
|
||||
|
||||
return Json(stats);
|
||||
}
|
||||
|
||||
private static string GetTimeAgo(DateTime timestamp)
|
||||
{
|
||||
var span = DateTime.UtcNow - timestamp;
|
||||
|
||||
if (span.TotalSeconds < 60)
|
||||
return "just now";
|
||||
if (span.TotalMinutes < 60)
|
||||
return $"{(int)span.TotalMinutes}m ago";
|
||||
if (span.TotalHours < 24)
|
||||
return $"{(int)span.TotalHours}h ago";
|
||||
if (span.TotalDays < 7)
|
||||
return $"{(int)span.TotalDays}d ago";
|
||||
|
||||
return timestamp.ToString("MMM dd");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user