littleshop/LittleShop/Areas/Admin/Controllers/BotActivityController.cs
SysAdmin 5530f9e4f5 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>
2025-09-24 23:00:20 +01:00

142 lines
4.4 KiB
C#

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");
}
}