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>
142 lines
4.4 KiB
C#
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");
|
|
}
|
|
} |