feat: Modularize event management system - 98.7% reduction in main file size
BREAKING CHANGES: - Refactored monolithic manage.astro (7,623 lines) into modular architecture - Original file backed up as manage-old.astro NEW ARCHITECTURE: ✅ 5 Utility Libraries: - event-management.ts: Event data operations & formatting - ticket-management.ts: Ticket CRUD operations & sales data - seating-management.ts: Seating map management & layout generation - sales-analytics.ts: Sales metrics, reporting & data export - marketing-kit.ts: Marketing asset generation & social media ✅ 5 Shared Components: - TicketTypeModal.tsx: Reusable ticket type creation/editing - SeatingMapModal.tsx: Advanced seating map editor with drag-and-drop - EmbedCodeModal.tsx: Widget embedding with customization - OrdersTable.tsx: Comprehensive orders table with sorting/pagination - AttendeesTable.tsx: Attendee management with export capabilities ✅ 11 Tab Components: - TicketsTab.tsx: Ticket management with card/list views - VenueTab.tsx: Seating map management & venue configuration - OrdersTab.tsx: Sales data & order management - AttendeesTab.tsx: Attendee check-in & management - PresaleTab.tsx: Presale code generation & tracking - DiscountTab.tsx: Discount code management - AddonsTab.tsx: Add-on product management - PrintedTab.tsx: Printed ticket barcode management - SettingsTab.tsx: Event configuration & custom fields - MarketingTab.tsx: Marketing kit with social media templates - PromotionsTab.tsx: Campaign & promotion management ✅ 4 Infrastructure Components: - TabNavigation.tsx: Responsive tab navigation system - EventManagement.tsx: Main orchestration component - EventHeader.astro: Event information header - QuickStats.astro: Statistics dashboard BENEFITS: - 98.7% reduction in main file size (7,623 → ~100 lines) - Dramatic improvement in maintainability and team collaboration - Component-level testing now possible - Reusable components across multiple features - Lazy loading support for better performance - Full TypeScript support with proper interfaces - Separation of concerns: business logic separated from UI 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
124
src/components/QuickStats.astro
Normal file
124
src/components/QuickStats.astro
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
interface Props {
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
const { eventId } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6 shadow-lg hover:shadow-xl hover:scale-105 transition-all duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white/80 uppercase tracking-wide">Tickets Sold</p>
|
||||
<p id="tickets-sold" class="text-3xl font-light text-white mt-1">0</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-emerald-500/20 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-emerald-400" 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>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6 shadow-lg hover:shadow-xl hover:scale-105 transition-all duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white/80 uppercase tracking-wide">Available</p>
|
||||
<p id="tickets-available" class="text-3xl font-light text-white mt-1">--</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-blue-400" 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>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6 shadow-lg hover:shadow-xl hover:scale-105 transition-all duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white/80 uppercase tracking-wide">Check-ins</p>
|
||||
<p id="checked-in" class="text-3xl font-light text-white mt-1">0</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-purple-400" 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>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6 shadow-lg hover:shadow-xl hover:scale-105 transition-all duration-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-white/80 uppercase tracking-wide">Net Revenue</p>
|
||||
<p id="net-revenue" class="text-3xl font-light text-white mt-1">$0</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-amber-500/20 rounded-xl flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-amber-400" 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();
|
||||
});
|
||||
|
||||
async function loadQuickStats() {
|
||||
try {
|
||||
const { createClient } = await import('@supabase/supabase-js');
|
||||
const supabase = createClient(
|
||||
import.meta.env.PUBLIC_SUPABASE_URL,
|
||||
import.meta.env.PUBLIC_SUPABASE_ANON_KEY
|
||||
);
|
||||
|
||||
// Load ticket sales data
|
||||
const { data: tickets } = await supabase
|
||||
.from('tickets')
|
||||
.select(`
|
||||
id,
|
||||
price_paid,
|
||||
checked_in,
|
||||
ticket_types (
|
||||
id,
|
||||
quantity
|
||||
)
|
||||
`)
|
||||
.eq('event_id', eventId)
|
||||
.eq('status', 'confirmed');
|
||||
|
||||
// Load ticket types for capacity calculation
|
||||
const { data: ticketTypes } = await supabase
|
||||
.from('ticket_types')
|
||||
.select('id, quantity')
|
||||
.eq('event_id', eventId)
|
||||
.eq('is_active', true);
|
||||
|
||||
// Calculate stats
|
||||
const ticketsSold = tickets?.length || 0;
|
||||
const totalRevenue = tickets?.reduce((sum, ticket) => sum + ticket.price_paid, 0) || 0;
|
||||
const netRevenue = totalRevenue * 0.97; // Assuming 3% platform fee
|
||||
const checkedIn = tickets?.filter(ticket => ticket.checked_in).length || 0;
|
||||
const totalCapacity = ticketTypes?.reduce((sum, type) => sum + type.quantity, 0) || 0;
|
||||
const ticketsAvailable = totalCapacity - ticketsSold;
|
||||
|
||||
// Update UI
|
||||
document.getElementById('tickets-sold').textContent = ticketsSold.toString();
|
||||
document.getElementById('tickets-available').textContent = ticketsAvailable.toString();
|
||||
document.getElementById('checked-in').textContent = checkedIn.toString();
|
||||
document.getElementById('net-revenue').textContent = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD'
|
||||
}).format(netRevenue / 100);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading quick stats:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user