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>
362 lines
13 KiB
Plaintext
362 lines
13 KiB
Plaintext
@{
|
|
ViewData["Title"] = "Live Bot Activity";
|
|
Layout = "_Layout";
|
|
}
|
|
|
|
<style>
|
|
.activity-feed {
|
|
max-height: 600px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.activity-item {
|
|
transition: all 0.3s ease;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.activity-item:hover {
|
|
background-color: rgba(0, 123, 255, 0.05);
|
|
border-left-color: #007bff;
|
|
}
|
|
|
|
.activity-item.new {
|
|
animation: slideIn 0.5s ease;
|
|
background-color: rgba(40, 167, 69, 0.1);
|
|
}
|
|
|
|
@@keyframes slideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(-20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
|
|
.user-bubble {
|
|
display: inline-block;
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.stats-card {
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.stats-card:hover {
|
|
transform: translateY(-5px);
|
|
}
|
|
|
|
.activity-type-badge {
|
|
font-size: 0.75rem;
|
|
padding: 2px 8px;
|
|
}
|
|
|
|
.pulse {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@@keyframes pulse {
|
|
0% {
|
|
box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7);
|
|
}
|
|
70% {
|
|
box-shadow: 0 0 0 10px rgba(40, 167, 69, 0);
|
|
}
|
|
100% {
|
|
box-shadow: 0 0 0 0 rgba(40, 167, 69, 0);
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="container-fluid">
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<h1><i class="fas fa-satellite-dish"></i> Live Bot Activity</h1>
|
|
<p class="text-muted">Real-time view of customer interactions across all bots</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="d-flex align-items-center">
|
|
<span class="badge bg-success pulse me-2">LIVE</span>
|
|
<small class="text-muted">Updates every 5 seconds</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card stats-card border-primary">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="text-muted">Active Users</h6>
|
|
<h2 class="mb-0" id="activeUsersCount">0</h2>
|
|
</div>
|
|
<div class="text-primary">
|
|
<i class="fas fa-users fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stats-card border-success">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="text-muted">Today's Activities</h6>
|
|
<h2 class="mb-0" id="todayActivitiesCount">0</h2>
|
|
</div>
|
|
<div class="text-success">
|
|
<i class="fas fa-chart-line fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stats-card border-info">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="text-muted">Unique Visitors</h6>
|
|
<h2 class="mb-0" id="uniqueVisitorsCount">0</h2>
|
|
</div>
|
|
<div class="text-info">
|
|
<i class="fas fa-fingerprint fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stats-card border-warning">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="text-muted">Cart Actions</h6>
|
|
<h2 class="mb-0" id="cartActionsCount">0</h2>
|
|
</div>
|
|
<div class="text-warning">
|
|
<i class="fas fa-shopping-cart fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Active Users Panel -->
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header bg-primary text-white">
|
|
<i class="fas fa-user-clock"></i> Active Users (Last 5 min)
|
|
</div>
|
|
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
|
|
<div class="list-group list-group-flush" id="activeUsersList">
|
|
<!-- Active users will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Popular Products -->
|
|
<div class="card mt-3">
|
|
<div class="card-header bg-success text-white">
|
|
<i class="fas fa-fire"></i> Trending Products (7 days)
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="list-group list-group-flush" id="popularProductsList">
|
|
<!-- Popular products will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Activity Feed -->
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header bg-dark text-white">
|
|
<i class="fas fa-stream"></i> Live Activity Feed
|
|
</div>
|
|
<div class="card-body activity-feed p-0" id="activityFeed">
|
|
<!-- Activities will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
let lastActivityId = '';
|
|
|
|
function getActivityIcon(type) {
|
|
const icons = {
|
|
'Browse': 'fa-eye',
|
|
'ViewProduct': 'fa-search',
|
|
'AddToCart': 'fa-cart-plus',
|
|
'RemoveFromCart': 'fa-cart-arrow-down',
|
|
'Checkout': 'fa-cash-register',
|
|
'Search': 'fa-search',
|
|
'Register': 'fa-user-plus',
|
|
'Login': 'fa-sign-in-alt'
|
|
};
|
|
return icons[type] || 'fa-circle';
|
|
}
|
|
|
|
function getActivityColor(type) {
|
|
const colors = {
|
|
'Browse': 'info',
|
|
'ViewProduct': 'primary',
|
|
'AddToCart': 'success',
|
|
'RemoveFromCart': 'warning',
|
|
'Checkout': 'danger',
|
|
'Search': 'secondary'
|
|
};
|
|
return colors[type] || 'secondary';
|
|
}
|
|
|
|
function updateActivities() {
|
|
// Get recent activities
|
|
$.get('@Url.Action("GetRecentActivities")', { count: 30 }, function(activities) {
|
|
const feed = $('#activityFeed');
|
|
|
|
activities.forEach(function(activity) {
|
|
const existingItem = $(`#activity-${activity.id}`);
|
|
if (existingItem.length === 0) {
|
|
const isNew = lastActivityId && activity.id !== lastActivityId;
|
|
const html = `
|
|
<div id="activity-${activity.id}" class="activity-item ${isNew ? 'new' : ''} p-3 border-bottom">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div class="d-flex">
|
|
<div class="me-3 text-${getActivityColor(activity.activityType)}">
|
|
<i class="fas ${getActivityIcon(activity.activityType)} fa-lg"></i>
|
|
</div>
|
|
<div>
|
|
<div class="mb-1">
|
|
<span class="user-bubble">${activity.userDisplayName}</span>
|
|
<span class="activity-type-badge badge bg-${getActivityColor(activity.activityType)} ms-2">
|
|
${activity.activityType}
|
|
</span>
|
|
</div>
|
|
<div class="text-dark">
|
|
${activity.activityDescription}
|
|
</div>
|
|
${activity.productName ? `
|
|
<div class="text-muted small mt-1">
|
|
<i class="fas fa-box"></i> ${activity.productName}
|
|
${activity.value ? ` - £${activity.value.toFixed(2)}` : ''}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="text-muted small">
|
|
${activity.timeAgo}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
feed.prepend(html);
|
|
|
|
// Limit feed to 30 items
|
|
while (feed.children().length > 30) {
|
|
feed.children().last().remove();
|
|
}
|
|
}
|
|
});
|
|
|
|
if (activities.length > 0) {
|
|
lastActivityId = activities[0].id;
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateActiveUsers() {
|
|
$.get('@Url.Action("GetActiveUsers")', function(users) {
|
|
const list = $('#activeUsersList');
|
|
list.empty();
|
|
|
|
$('#activeUsersCount').text(users.length);
|
|
|
|
users.forEach(function(user) {
|
|
const html = `
|
|
<div class="list-group-item">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h6 class="mb-1">${user.userName}</h6>
|
|
<small class="text-muted">${user.lastAction}</small>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="badge bg-primary">${user.activityCount} actions</span>
|
|
${user.totalValue > 0 ? `<br><small class="text-success">£${user.totalValue.toFixed(2)}</small>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
list.append(html);
|
|
});
|
|
|
|
if (users.length === 0) {
|
|
list.append('<div class="list-group-item text-muted text-center">No active users</div>');
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateStatistics() {
|
|
$.get('@Url.Action("GetStatistics")', function(stats) {
|
|
$('#todayActivitiesCount').text(stats.todayActivities);
|
|
$('#uniqueVisitorsCount').text(stats.uniqueUsersToday);
|
|
|
|
// Update popular products
|
|
const productsList = $('#popularProductsList');
|
|
productsList.empty();
|
|
|
|
stats.popularProducts.forEach(function(product) {
|
|
const html = `
|
|
<div class="list-group-item">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="mb-0">${product.productName}</h6>
|
|
</div>
|
|
<div>
|
|
<span class="badge bg-info">${product.viewCount} views</span>
|
|
<span class="badge bg-success">${product.addToCartCount} carts</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
productsList.append(html);
|
|
});
|
|
|
|
// Calculate cart actions
|
|
let cartActions = 0;
|
|
$('#activityFeed .activity-item').each(function() {
|
|
const text = $(this).text();
|
|
if (text.includes('AddToCart') || text.includes('Checkout')) {
|
|
cartActions++;
|
|
}
|
|
});
|
|
$('#cartActionsCount').text(cartActions);
|
|
});
|
|
}
|
|
|
|
// Initial load and periodic updates
|
|
$(document).ready(function() {
|
|
updateActivities();
|
|
updateActiveUsers();
|
|
updateStatistics();
|
|
|
|
// Update every 5 seconds
|
|
setInterval(updateActivities, 5000);
|
|
setInterval(updateActiveUsers, 10000);
|
|
setInterval(updateStatistics, 15000);
|
|
});
|
|
</script>
|
|
} |