Major additions: - Territory manager system with application workflow - Custom pricing and page builder with Craft.js - Enhanced Stripe Connect onboarding - CodeReadr QR scanning integration - Kiosk mode for venue sales - Super admin dashboard and analytics - MCP integration for AI-powered operations Infrastructure improvements: - Centralized API client and routing system - Enhanced authentication with organization context - Comprehensive theme management system - Advanced event management with custom tabs - Performance monitoring and accessibility features Database schema updates: - Territory management tables - Custom pages and pricing structures - Kiosk PIN system - Enhanced organization profiles - CodeReadr integration tables 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
239 lines
11 KiB
Plaintext
239 lines
11 KiB
Plaintext
---
|
|
interface Props {
|
|
eventId: string;
|
|
}
|
|
|
|
const { eventId } = Astro.props;
|
|
---
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
<!-- Tickets Sold Card -->
|
|
<div class="rounded-2xl p-6 shadow-lg premium-hover cursor-pointer group ring-1 transition-all duration-200 hover:shadow-xl hover:ring-2"
|
|
style="background: var(--success-bg); border: 1px solid var(--success-border);">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-semibold uppercase tracking-wide" style="color: var(--success-color);">Tickets Sold</p>
|
|
<div class="flex items-center mt-2">
|
|
<p id="tickets-sold" class="text-3xl font-light animate-countUp" style="color: var(--success-color);">0</p>
|
|
<div id="tickets-sold-loading" class="skeleton w-16 h-8 ml-2 hidden"></div>
|
|
</div>
|
|
<div class="flex items-center mt-1">
|
|
<span id="tickets-sold-trend" class="text-xs flex items-center" style="color: var(--success-color); opacity: 0.8;">
|
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
|
</svg>
|
|
+12% from last week
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-14 h-14 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform"
|
|
style="background: var(--success-bg); backdrop-filter: blur(8px);">
|
|
<svg class="w-7 h-7" style="color: var(--success-color);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Available Tickets Card -->
|
|
<div class="rounded-2xl p-6 shadow-lg premium-hover cursor-pointer group ring-1 transition-all duration-200 hover:shadow-xl hover:ring-2"
|
|
style="background: var(--glass-bg-lg); border: 1px solid var(--glass-border);">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-semibold uppercase tracking-wide" style="color: var(--glass-text-secondary);">Available</p>
|
|
<div class="flex items-center mt-2">
|
|
<p id="tickets-available" class="text-3xl font-light animate-countUp" style="color: var(--glass-text-primary);">--</p>
|
|
<div id="tickets-available-loading" class="skeleton w-16 h-8 ml-2 hidden"></div>
|
|
</div>
|
|
<div class="flex items-center mt-1">
|
|
<span id="tickets-available-trend" class="text-xs flex items-center" style="color: var(--glass-text-tertiary);">
|
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
|
</svg>
|
|
Ready to sell
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-14 h-14 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform"
|
|
style="background: var(--glass-bg-elevated); backdrop-filter: blur(8px);">
|
|
<svg class="w-7 h-7" style="color: var(--glass-text-accent);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Check-ins Card -->
|
|
<div class="rounded-2xl p-6 shadow-lg premium-hover cursor-pointer group ring-1 transition-all duration-200 hover:shadow-xl hover:ring-2"
|
|
style="background: var(--warning-bg); border: 1px solid var(--warning-border);">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-semibold uppercase tracking-wide" style="color: var(--warning-color);">Check-ins</p>
|
|
<div class="flex items-center mt-2">
|
|
<p id="checked-in" class="text-3xl font-light animate-countUp" style="color: var(--warning-color);">0</p>
|
|
<div id="checked-in-loading" class="skeleton w-16 h-8 ml-2 hidden"></div>
|
|
</div>
|
|
<div class="flex items-center mt-1">
|
|
<span id="checked-in-trend" class="text-xs flex items-center" style="color: var(--warning-color); opacity: 0.8;">
|
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
85% attendance rate
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-14 h-14 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform"
|
|
style="background: var(--warning-bg); backdrop-filter: blur(8px);">
|
|
<svg class="w-7 h-7" style="color: var(--warning-color);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Net Revenue Card -->
|
|
<div class="rounded-2xl p-6 shadow-lg premium-hover cursor-pointer group ring-1 transition-all duration-200 hover:shadow-xl hover:ring-2"
|
|
style="background: var(--glass-bg-button); border: 1px solid var(--glass-border);">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-semibold uppercase tracking-wide" style="color: var(--glass-text-accent);">Net Revenue</p>
|
|
<div class="flex items-center mt-2">
|
|
<p id="net-revenue" class="text-3xl font-light animate-countUp" style="color: var(--glass-text-accent);">$0</p>
|
|
<div id="net-revenue-loading" class="skeleton w-20 h-8 ml-2 hidden"></div>
|
|
</div>
|
|
<div class="flex items-center mt-1">
|
|
<span id="net-revenue-trend" class="text-xs flex items-center" style="color: var(--glass-text-accent); opacity: 0.8;">
|
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
|
</svg>
|
|
+24% this month
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-14 h-14 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform"
|
|
style="background: var(--glass-bg-elevated); backdrop-filter: blur(8px);">
|
|
<svg class="w-7 h-7" style="color: var(--glass-text-accent);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script define:vars={{ eventId }}>
|
|
// Initialize quick stats when page loads
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await loadQuickStats();
|
|
});
|
|
|
|
// Count-up animation function
|
|
function animateCountUp(element, finalValue, duration = 1000, isRevenue = false) {
|
|
const startValue = 0;
|
|
const startTime = Date.now();
|
|
|
|
function updateCount() {
|
|
const elapsed = Date.now() - startTime;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
|
|
// Use easing function for smooth animation
|
|
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
|
const currentValue = Math.floor(startValue + (finalValue - startValue) * easeOutQuart);
|
|
|
|
if (isRevenue) {
|
|
element.textContent = formatCurrency(currentValue);
|
|
} else {
|
|
element.textContent = currentValue.toString();
|
|
}
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(updateCount);
|
|
} else {
|
|
element.textContent = isRevenue ? formatCurrency(finalValue) : finalValue.toString();
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(updateCount);
|
|
}
|
|
|
|
// Simple currency formatter
|
|
function formatCurrency(amount) {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: 'USD',
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0
|
|
}).format(amount);
|
|
}
|
|
|
|
// Show skeleton loading
|
|
function showSkeleton(statType) {
|
|
const loadingElement = document.getElementById(`${statType}-loading`);
|
|
const valueElement = document.getElementById(statType);
|
|
|
|
if (loadingElement && valueElement) {
|
|
valueElement.classList.add('hidden');
|
|
loadingElement.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
// Hide skeleton loading
|
|
function hideSkeleton(statType) {
|
|
const loadingElement = document.getElementById(`${statType}-loading`);
|
|
const valueElement = document.getElementById(statType);
|
|
|
|
if (loadingElement && valueElement) {
|
|
loadingElement.classList.add('hidden');
|
|
valueElement.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
async function loadQuickStats() {
|
|
try {
|
|
|
|
// Show skeleton loading states
|
|
showSkeleton('tickets-sold');
|
|
showSkeleton('tickets-available');
|
|
showSkeleton('checked-in');
|
|
showSkeleton('net-revenue');
|
|
|
|
const { api } = await import('/src/lib/api-router.js');
|
|
|
|
// Load event statistics using the new API system
|
|
const stats = await api.loadEventStats(eventId);
|
|
|
|
if (!stats) {
|
|
return;
|
|
}
|
|
|
|
// Hide skeleton loading and animate values
|
|
setTimeout(() => {
|
|
hideSkeleton('tickets-sold');
|
|
animateCountUp(document.getElementById('tickets-sold'), stats.ticketsSold, 1200);
|
|
}, 300);
|
|
|
|
setTimeout(() => {
|
|
hideSkeleton('tickets-available');
|
|
animateCountUp(document.getElementById('tickets-available'), stats.ticketsAvailable, 1000);
|
|
}, 500);
|
|
|
|
setTimeout(() => {
|
|
hideSkeleton('checked-in');
|
|
animateCountUp(document.getElementById('checked-in'), stats.checkedIn, 1400);
|
|
}, 700);
|
|
|
|
setTimeout(() => {
|
|
hideSkeleton('net-revenue');
|
|
// Convert cents to dollars for display
|
|
const revenueInDollars = Math.round(stats.netRevenue / 100);
|
|
animateCountUp(document.getElementById('net-revenue'), revenueInDollars, 1600, true);
|
|
}, 900);
|
|
|
|
} catch (error) {
|
|
// Hide all skeleton states on error
|
|
hideSkeleton('tickets-sold');
|
|
hideSkeleton('tickets-available');
|
|
hideSkeleton('checked-in');
|
|
hideSkeleton('net-revenue');
|
|
}
|
|
}
|
|
</script> |