feat: add advanced analytics and territory management system

- Add comprehensive analytics components with export functionality
- Implement territory management with manager performance tracking
- Add seatmap components for venue layout management
- Create customer management features with modal interface
- Add advanced hooks for dashboard flags and territory data
- Implement seat selection and venue management utilities
- Add type definitions for ticketing and seatmap systems

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-26 09:25:10 -06:00
parent d5c3953888
commit aa81eb5adb
438 changed files with 90509 additions and 2787 deletions

View File

@@ -0,0 +1,335 @@
/**
* Lazy-loaded route components for code splitting
* These components are loaded on-demand to improve initial bundle size
*/
import { lazy } from 'react';
import { motion } from 'framer-motion';
import {
Calendar,
MapPin,
Users,
Settings,
Shield,
CreditCard,
QrCode,
Scan
} from 'lucide-react';
import { Card, CardHeader, CardBody } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { Skeleton } from '@/components/loading/Skeleton';
// Lazy-loaded components
export const EventDetailPage = lazy(() => import('@/pages/EventDetailPage'));
export const GateOpsPage = lazy(() => import('@/pages/GateOpsPage').then(module => ({ default: module.GateOpsPage })));
export const PaymentSettings = lazy(() => import('@/features/org/PaymentSettings').then(module => ({ default: module.PaymentSettings })));
export const ScannerPage = lazy(() => import('@/features/scanner/ScannerPage').then(module => ({ default: module.ScannerPage })));
// Skeleton components for Suspense fallbacks
/**
* Event detail page skeleton with event header, stats, and ticket types
*/
export function EventDetailPageSkeleton() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="space-y-6"
data-testid="event-detail-skeleton"
>
{/* Event header */}
<Card className="overflow-hidden">
<div className="bg-gradient-to-r from-primary-500 to-accent-500 p-6">
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
<div className="space-y-3 text-white">
<Skeleton.Base className="h-8 w-64 bg-white/20" />
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
<Calendar className="w-4 h-4" />
<Skeleton.Base className="h-4 w-32 bg-white/20" />
</div>
<div className="flex items-center space-x-2">
<MapPin className="w-4 h-4" />
<Skeleton.Base className="h-4 w-24 bg-white/20" />
</div>
</div>
</div>
<div className="mt-4 md:mt-0 flex space-x-3">
<Skeleton.Button size="md" className="bg-white/20" />
<Skeleton.Button size="md" className="bg-white/20" />
</div>
</div>
</div>
</Card>
{/* Event stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{Array.from({ length: 4 }).map((_, index) => (
<Card key={index} className="p-6">
<div className="space-y-3">
<div className="flex items-center space-x-2">
<Users className="w-5 h-5 text-text-secondary" />
<Skeleton.Base className="h-4 w-20" />
</div>
<Skeleton.Base className="h-8 w-16" />
<Skeleton.Base className="h-3 w-32" />
</div>
</Card>
))}
</div>
{/* Ticket types section */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<Skeleton.Base className="h-6 w-32" />
<Skeleton.Button size="md" />
</div>
</CardHeader>
<CardBody>
<div className="space-y-4">
{Array.from({ length: 3 }).map((_, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-glass-bg border border-glass-border rounded-lg">
<div className="flex-1 space-y-2">
<Skeleton.Base className="h-5 w-40" />
<Skeleton.Base className="h-4 w-24" />
</div>
<div className="flex items-center space-x-4">
<Badge variant="neutral" className="bg-glass-bg">
<Skeleton.Base className="h-4 w-12" />
</Badge>
<Skeleton.Base className="h-6 w-16" />
<Skeleton.Button size="sm" />
</div>
</div>
))}
</div>
</CardBody>
</Card>
</motion.div>
);
}
/**
* Gate operations page skeleton with live scanning interface
*/
export function GateOpsPageSkeleton() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="space-y-6"
data-testid="gate-ops-skeleton"
>
{/* Status header */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{Array.from({ length: 3 }).map((_, index) => (
<Card key={index} className="p-6">
<div className="space-y-3">
<div className="flex items-center space-x-2">
<Shield className="w-5 h-5 text-text-secondary" />
<Skeleton.Base className="h-4 w-24" />
</div>
<Skeleton.Base className="h-8 w-20" />
<div className="flex items-center space-x-2">
<Badge variant="neutral" className="bg-glass-bg">
<Skeleton.Base className="h-3 w-12" />
</Badge>
</div>
</div>
</Card>
))}
</div>
{/* Control panel */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<Skeleton.Base className="h-6 w-32" />
<div className="flex items-center space-x-3">
<Badge variant="neutral" className="bg-glass-bg">
<Skeleton.Base className="h-4 w-16" />
</Badge>
<Skeleton.Button size="sm" />
</div>
</div>
</CardHeader>
<CardBody>
<div className="space-y-4">
{Array.from({ length: 5 }).map((_, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-glass-bg border border-glass-border rounded">
<div className="flex items-center space-x-3">
<Skeleton.Avatar size="sm" />
<div className="space-y-1">
<Skeleton.Base className="h-4 w-24" />
<Skeleton.Base className="h-3 w-16" />
</div>
</div>
<div className="flex items-center space-x-2">
<Badge variant="neutral" className="bg-glass-bg">
<Skeleton.Base className="h-3 w-8" />
</Badge>
<Skeleton.Base className="h-4 w-12" />
</div>
</div>
))}
</div>
</CardBody>
</Card>
</motion.div>
);
}
/**
* Payment settings page skeleton with Stripe integration status
*/
export function PaymentSettingsPageSkeleton() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="space-y-6"
data-testid="payment-settings-skeleton"
>
{/* Connection status card */}
<Card className="p-6">
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<CreditCard className="w-6 h-6 text-text-secondary" />
<Skeleton.Base className="h-6 w-48" />
</div>
<Badge variant="neutral" className="bg-glass-bg">
<Skeleton.Base className="h-4 w-16" />
</Badge>
</div>
<div className="bg-glass-bg border border-glass-border rounded-lg p-6">
<div className="space-y-4">
<Skeleton.Base className="h-5 w-56" />
<Skeleton.Text lines={3} />
<div className="flex space-x-3 pt-4">
<Skeleton.Button size="md" />
<Skeleton.Button size="md" />
</div>
</div>
</div>
</div>
</Card>
{/* Payment configuration */}
<Card>
<CardHeader>
<Skeleton.Base className="h-6 w-40" />
</CardHeader>
<CardBody>
<div className="space-y-6">
{/* Form fields */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{Array.from({ length: 4 }).map((_, index) => (
<div key={index} className="space-y-2">
<Skeleton.Base className="h-4 w-24" />
<Skeleton.Base className="h-10 w-full" />
</div>
))}
</div>
{/* Settings toggles */}
<div className="space-y-4 pt-6 border-t border-glass-border">
{Array.from({ length: 3 }).map((_, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-glass-bg border border-glass-border rounded">
<div className="space-y-1">
<Skeleton.Base className="h-4 w-32" />
<Skeleton.Base className="h-3 w-48" />
</div>
<Skeleton.Base className="h-6 w-12 rounded-full" />
</div>
))}
</div>
</div>
</CardBody>
</Card>
</motion.div>
);
}
/**
* Scanner page skeleton with camera interface
*/
export function ScannerPageSkeleton() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="min-h-screen bg-glass-bg"
data-testid="scanner-skeleton"
>
{/* Scanner header */}
<div className="bg-gradient-to-r from-primary-500 to-accent-500 p-6 text-white">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<QrCode className="w-6 h-6" />
<Skeleton.Base className="h-6 w-32 bg-white/20" />
</div>
<div className="flex items-center space-x-2">
<Badge variant="neutral" className="bg-white/20 border-white/30">
<Skeleton.Base className="h-3 w-12 bg-white/20" />
</Badge>
<Skeleton.Button size="sm" className="bg-white/20" />
</div>
</div>
</div>
<div className="container mx-auto px-6 py-8 space-y-6">
{/* Camera viewport */}
<Card className="overflow-hidden">
<div className="aspect-video bg-background-secondary border-2 border-dashed border-glass-border flex items-center justify-center">
<div className="text-center space-y-3">
<Scan className="w-12 h-12 text-text-secondary mx-auto" />
<Skeleton.Base className="h-4 w-32 mx-auto" />
</div>
</div>
</Card>
{/* Scanner controls */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{Array.from({ length: 4 }).map((_, index) => (
<Card key={index} className="p-4">
<div className="text-center space-y-2">
<Settings className="w-5 h-5 text-text-secondary mx-auto" />
<Skeleton.Base className="h-4 w-16 mx-auto" />
</div>
</Card>
))}
</div>
{/* Recent scans */}
<Card>
<CardHeader>
<Skeleton.Base className="h-6 w-32" />
</CardHeader>
<CardBody>
<div className="space-y-3">
{Array.from({ length: 5 }).map((_, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-glass-bg border border-glass-border rounded">
<div className="flex items-center space-x-3">
<Skeleton.Avatar size="sm" />
<div className="space-y-1">
<Skeleton.Base className="h-4 w-20" />
<Skeleton.Base className="h-3 w-16" />
</div>
</div>
<Badge variant="neutral" className="bg-glass-bg">
<Skeleton.Base className="h-3 w-8" />
</Badge>
</div>
))}
</div>
</CardBody>
</Card>
</div>
</motion.div>
);
}