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,55 @@
import React from 'react';
import { Skeleton } from '@/components/loading/Skeleton';
interface EventCardsSkeletonProps {
count?: number;
className?: string;
}
/**
* Loading skeleton for event cards in responsive grid
* Matches the layout of the actual EventCard components
*/
export const EventCardsSkeleton: React.FC<EventCardsSkeletonProps> = ({
count = 6,
className = ''
}) => {
const skeletonCards = Array.from({ length: count }, (_, index) => (
<div
key={`skeleton-${index}`}
className="bg-glass-bg backdrop-blur-lg border border-glass-border rounded-lg p-md space-y-md"
>
{/* Event name */}
<Skeleton.Base className="h-5 w-3/4" />
{/* Date range */}
<Skeleton.Base className="h-4 w-1/2" />
{/* Venue */}
<Skeleton.Base className="h-4 w-2/3" />
{/* Footer with status and territory */}
<div className="flex items-center justify-between pt-sm border-t border-glass-border">
{/* Status badge */}
<Skeleton.Base className="h-6 w-16 rounded-full" />
{/* Territory chip */}
<Skeleton.Base className="h-5 w-12 rounded" />
</div>
{/* Ticket types count (optional) */}
<div className="text-center">
<Skeleton.Base className="h-3 w-20 mx-auto" />
</div>
</div>
));
return (
<div className={`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-lg ${className}`}>
{skeletonCards}
</div>
);
};
export default EventCardsSkeleton;

View File

@@ -0,0 +1,174 @@
import React from 'react';
import { clsx } from 'clsx';
import { Card, CardBody, CardHeader } from '@/components/ui/Card';
import { Skeleton } from '@/components/loading/Skeleton';
interface EventDetailSkeletonProps {
className?: string;
}
/**
* Loading skeleton for event detail page
* Matches the complex layout of event details with tabs, stats, and ticket information
*/
export const EventDetailSkeleton: React.FC<EventDetailSkeletonProps> = ({
className = ''
}) => {
return (
<div className={clsx('space-y-8', className)}>
{/* Page Header */}
<div className="flex items-start justify-between">
<div className="space-y-2">
<Skeleton.Base className="h-8 w-96" />
<Skeleton.Base className="h-4 w-64" />
</div>
<div className="flex space-x-3">
<Skeleton.Button size="md" />
<Skeleton.Button size="md" />
</div>
</div>
{/* Event Hero Banner */}
<Card className="overflow-hidden">
<div className="relative">
<Skeleton.Base className="h-64 w-full" />
{/* Overlay content */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute bottom-4 left-6 right-6">
<div className="space-y-3">
<div className="flex items-center space-x-3">
<Skeleton.Base className="h-6 w-20 rounded-full bg-white/20" />
<Skeleton.Base className="h-6 w-24 rounded-full bg-white/20" />
</div>
<Skeleton.Base className="h-8 w-80 bg-white/20" />
<div className="flex items-center space-x-4 text-white/80">
<div className="flex items-center space-x-2">
<Skeleton.Base className="h-4 w-4 bg-white/20" />
<Skeleton.Base className="h-4 w-20 bg-white/20" />
</div>
<div className="flex items-center space-x-2">
<Skeleton.Base className="h-4 w-4 bg-white/20" />
<Skeleton.Base className="h-4 w-24 bg-white/20" />
</div>
</div>
</div>
</div>
</div>
</Card>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{Array.from({ length: 4 }).map((_, index) => (
<Card key={`stat-${index}`} className="surface-card">
<CardBody className="p-6 text-center">
<div className="space-y-3">
<div className="mx-auto w-12 h-12 bg-glass-bg rounded-full flex items-center justify-center">
<Skeleton.Base className="h-6 w-6" />
</div>
<Skeleton.Base className="h-8 w-16 mx-auto" />
<Skeleton.Base className="h-4 w-20 mx-auto" />
</div>
</CardBody>
</Card>
))}
</div>
{/* Main Content Grid */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Main Content */}
<div className="lg:col-span-2 space-y-6">
{/* Event Description */}
<Card className="surface-card">
<CardHeader className="pb-4">
<Skeleton.Base className="h-6 w-40" />
</CardHeader>
<CardBody className="space-y-3">
<Skeleton.Text lines={4} />
</CardBody>
</Card>
{/* Ticket Types */}
<Card className="surface-card">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<Skeleton.Base className="h-6 w-32" />
<Skeleton.Button size="sm" />
</div>
</CardHeader>
<CardBody className="space-y-4">
{Array.from({ length: 3 }).map((_, index) => (
<div key={`ticket-${index}`} className="bg-glass-bg border border-glass-border rounded-lg p-4">
<div className="flex items-center justify-between">
<div className="space-y-2">
<Skeleton.Base className="h-5 w-32" />
<Skeleton.Base className="h-4 w-48" />
<div className="flex items-center space-x-4">
<Skeleton.Base className="h-4 w-16" />
<Skeleton.Base className="h-4 w-20" />
</div>
</div>
<div className="text-right space-y-2">
<Skeleton.Base className="h-6 w-16" />
<Skeleton.Base className="h-4 w-12" />
</div>
</div>
</div>
))}
</CardBody>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Event Details */}
<Card className="surface-card">
<CardHeader className="pb-4">
<Skeleton.Base className="h-6 w-28" />
</CardHeader>
<CardBody className="space-y-4">
{Array.from({ length: 5 }).map((_, index) => (
<div key={`detail-${index}`} className="flex items-start space-x-3">
<Skeleton.Base className="h-5 w-5 flex-shrink-0" />
<div className="space-y-1 flex-1">
<Skeleton.Base className="h-4 w-24" />
<Skeleton.Base className="h-3 w-32" />
</div>
</div>
))}
</CardBody>
</Card>
{/* Quick Actions */}
<Card className="surface-card">
<CardHeader className="pb-4">
<Skeleton.Base className="h-6 w-32" />
</CardHeader>
<CardBody className="space-y-3">
{Array.from({ length: 4 }).map((_, index) => (
<Skeleton.Button key={`action-${index}`} size="md" className="w-full" />
))}
</CardBody>
</Card>
{/* Territory Filter */}
<Card className="surface-card">
<CardHeader className="pb-4">
<Skeleton.Base className="h-6 w-24" />
</CardHeader>
<CardBody className="space-y-3">
<Skeleton.Base className="h-10 w-full rounded-lg" />
<div className="flex flex-wrap gap-2">
{Array.from({ length: 2 }).map((_, index) => (
<Skeleton.Base key={`territory-${index}`} className="h-6 w-12 rounded-full" />
))}
</div>
</CardBody>
</Card>
</div>
</div>
</div>
);
};
export default EventDetailSkeleton;

View File

@@ -0,0 +1,122 @@
import React from 'react';
import { clsx } from 'clsx';
import { Card, CardBody } from '@/components/ui/Card';
import { Skeleton } from '@/components/loading/Skeleton';
interface FormSkeletonProps {
title?: boolean;
description?: boolean;
fields?: number;
textAreas?: number;
selects?: number;
checkboxes?: number;
hasActions?: boolean;
layout?: 'single' | 'two-column';
className?: string;
}
/**
* Loading skeleton for forms
* Provides flexible form field configuration matching typical form layouts
*/
export const FormSkeleton: React.FC<FormSkeletonProps> = ({
title = true,
description = false,
fields = 4,
textAreas = 1,
selects = 2,
checkboxes = 0,
hasActions = true,
layout = 'single',
className = ''
}) => {
const gridClasses = layout === 'two-column'
? 'grid grid-cols-1 md:grid-cols-2 gap-6'
: 'space-y-6';
return (
<Card className={clsx('surface-card', className)}>
<CardBody className="p-6">
<div className="space-y-6">
{/* Form title */}
{title && (
<div className="space-y-2">
<Skeleton.Base className="h-6 w-48" />
{description && <Skeleton.Base className="h-4 w-96" />}
</div>
)}
{/* Form fields grid */}
<div className={gridClasses}>
{/* Regular input fields */}
{Array.from({ length: fields }).map((_, index) => (
<div key={`field-${index}`} className="space-y-2">
<Skeleton.Base className="h-4 w-24" />
<Skeleton.Base className="h-10 w-full rounded-lg" />
</div>
))}
{/* Select dropdowns */}
{Array.from({ length: selects }).map((_, index) => (
<div key={`select-${index}`} className="space-y-2">
<Skeleton.Base className="h-4 w-20" />
<div className="relative">
<Skeleton.Base className="h-10 w-full rounded-lg" />
{/* Dropdown arrow */}
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<Skeleton.Base className="h-4 w-4" />
</div>
</div>
</div>
))}
</div>
{/* Text areas (full width) */}
{Array.from({ length: textAreas }).map((_, index) => (
<div key={`textarea-${index}`} className="space-y-2">
<Skeleton.Base className="h-4 w-32" />
<Skeleton.Base className="h-24 w-full rounded-lg" />
<Skeleton.Base className="h-3 w-20" />
</div>
))}
{/* Checkboxes */}
{checkboxes > 0 && (
<div className="space-y-3">
<Skeleton.Base className="h-4 w-28" />
{Array.from({ length: checkboxes }).map((_, index) => (
<div key={`checkbox-${index}`} className="flex items-center space-x-3">
<Skeleton.Base className="h-4 w-4 rounded" />
<Skeleton.Base className="h-4 w-32" />
</div>
))}
</div>
)}
{/* File upload area */}
<div className="space-y-2">
<Skeleton.Base className="h-4 w-24" />
<div className="border-2 border-dashed border-glass-border rounded-lg p-8">
<div className="text-center space-y-3">
<Skeleton.Base className="h-12 w-12 rounded-full mx-auto" />
<Skeleton.Base className="h-4 w-48 mx-auto" />
<Skeleton.Base className="h-3 w-32 mx-auto" />
</div>
</div>
</div>
{/* Form actions */}
{hasActions && (
<div className="flex justify-end space-x-3 pt-6 border-t border-glass-border">
<Skeleton.Button size="md" className="w-20" />
<Skeleton.Button size="md" className="w-24" />
</div>
)}
</div>
</CardBody>
</Card>
);
};
export default FormSkeleton;

View File

@@ -0,0 +1,55 @@
import React from 'react';
import { clsx } from 'clsx';
import { Card, CardBody } from '@/components/ui/Card';
import { Skeleton } from '@/components/loading/Skeleton';
interface KPISkeletonProps {
count?: number;
className?: string;
}
/**
* Loading skeleton for KPI/statistics cards
* Matches the layout of dashboard metric cards with icon, value, label, and change indicator
*/
export const KPISkeleton: React.FC<KPISkeletonProps> = ({
count = 4,
className = ''
}) => {
const skeletonCards = Array.from({ length: count }, (_, index) => (
<Card key={`kpi-skeleton-${index}`} className="h-full surface-card">
<CardBody className="p-6">
<div className="space-y-4">
{/* Header with icon and change indicator */}
<div className="flex items-center justify-between">
{/* Icon placeholder */}
<div className="p-2 bg-glass-bg rounded-lg">
<Skeleton.Base className="h-6 w-6" />
</div>
{/* Change indicator */}
<Skeleton.Base className="h-4 w-12 rounded-full" />
</div>
{/* Value and label */}
<div className="space-y-1">
{/* Large value */}
<Skeleton.Base className="h-8 w-24" />
{/* Label */}
<Skeleton.Base className="h-4 w-20" />
</div>
</div>
</CardBody>
</Card>
));
return (
<div className={clsx('grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4', className)}>
{skeletonCards}
</div>
);
};
export default KPISkeleton;

View File

@@ -0,0 +1,117 @@
import React from 'react';
import { clsx } from 'clsx';
import { Card, CardHeader, CardBody } from '@/components/ui/Card';
import { Skeleton } from '@/components/loading/Skeleton';
interface LoginSkeletonProps {
className?: string;
}
/**
* Loading skeleton for authentication state checks
* Matches the login page layout while auth context initializes
*/
export const LoginSkeleton: React.FC<LoginSkeletonProps> = ({
className = ''
}) => {
return (
<div className={clsx('min-h-screen bg-solid-with-pattern flex items-center justify-center', className)}>
<div className="w-full max-w-md p-6">
<Card className="backdrop-blur-lg bg-glass-bg border-glass-border">
<CardHeader className="text-center space-y-4 pb-4">
{/* Logo placeholder */}
<Skeleton.Base className="h-12 w-32 mx-auto rounded-lg" />
{/* Title and subtitle */}
<div className="space-y-2">
<Skeleton.Base className="h-7 w-48 mx-auto" />
<Skeleton.Base className="h-4 w-64 mx-auto" />
</div>
</CardHeader>
<CardBody className="space-y-6">
{/* Demo accounts section */}
<div className="space-y-4">
<Skeleton.Base className="h-5 w-32" />
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, index) => (
<div key={`demo-${index}`} className="bg-glass-bg border border-glass-border rounded-lg p-3">
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Skeleton.Base className="h-4 w-16 rounded-full" />
<Skeleton.Base className="h-3 w-24" />
</div>
<Skeleton.Base className="h-3 w-40" />
</div>
</div>
))}
</div>
</div>
{/* Login form skeleton */}
<div className="space-y-4">
{/* Email field */}
<div className="space-y-2">
<Skeleton.Base className="h-4 w-16" />
<div className="relative">
<Skeleton.Base className="h-12 w-full rounded-lg" />
<div className="absolute left-3 top-1/2 transform -translate-y-1/2">
<Skeleton.Base className="h-5 w-5" />
</div>
</div>
</div>
{/* Password field */}
<div className="space-y-2">
<Skeleton.Base className="h-4 w-20" />
<div className="relative">
<Skeleton.Base className="h-12 w-full rounded-lg" />
<div className="absolute left-3 top-1/2 transform -translate-y-1/2">
<Skeleton.Base className="h-5 w-5" />
</div>
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<Skeleton.Base className="h-5 w-5" />
</div>
</div>
</div>
{/* Remember me checkbox */}
<div className="flex items-center space-x-2">
<Skeleton.Base className="h-4 w-4 rounded" />
<Skeleton.Base className="h-4 w-24" />
</div>
{/* Login button */}
<Skeleton.Button size="lg" className="w-full h-12" />
</div>
{/* Footer link */}
<div className="text-center">
<Skeleton.Base className="h-4 w-40 mx-auto" />
</div>
</CardBody>
</Card>
{/* Loading indicator */}
<div className="text-center mt-6">
<div className="flex justify-center space-x-2">
{[0, 1, 2].map((index) => (
<div
key={index}
className="w-2 h-2 bg-accent-gold-500 rounded-full animate-bounce"
style={{
animationDelay: `${index * 0.2}s`,
animationDuration: '1s'
}}
/>
))}
</div>
<Skeleton.Base className="h-4 w-32 mx-auto mt-2" />
</div>
</div>
</div>
);
};
export default LoginSkeleton;

View File

@@ -0,0 +1,75 @@
import React from 'react';
import { clsx } from 'clsx';
import { Skeleton } from '@/components/loading/Skeleton';
interface OrganizationSkeletonProps {
className?: string;
}
/**
* Loading skeleton for organization initialization
* Shows a branded loading state while organization branding and settings are configured
*/
export const OrganizationSkeleton: React.FC<OrganizationSkeletonProps> = ({
className = ''
}) => {
return (
<div className={clsx('min-h-screen bg-org-canvas flex items-center justify-center', className)}>
<div className="text-center space-y-6 max-w-md w-full px-6">
{/* Logo placeholder */}
<div className="mx-auto mb-8">
<Skeleton.Base className="h-16 w-16 rounded-xl mx-auto" />
</div>
{/* Animated loading indicator */}
<div className="space-y-4">
{/* Three dot loading animation */}
<div className="flex justify-center space-x-2">
{[0, 1, 2].map((index) => (
<div
key={index}
className="w-3 h-3 bg-org-accent rounded-full animate-bounce"
style={{
animationDelay: `${index * 0.2}s`,
animationDuration: '1s'
}}
/>
))}
</div>
{/* Loading text */}
<div className="space-y-2">
<Skeleton.Base className="h-6 w-48 mx-auto" />
<Skeleton.Base className="h-4 w-64 mx-auto" />
</div>
</div>
{/* Progress indicators */}
<div className="space-y-3 pt-4">
<div className="flex items-center space-x-3">
<div className="w-2 h-2 bg-org-accent rounded-full animate-pulse" />
<Skeleton.Base className="h-3 w-32" />
</div>
<div className="flex items-center space-x-3">
<div className="w-2 h-2 bg-org-accent/60 rounded-full animate-pulse"
style={{ animationDelay: '0.5s' }} />
<Skeleton.Base className="h-3 w-40" />
</div>
<div className="flex items-center space-x-3">
<div className="w-2 h-2 bg-org-accent/30 rounded-full animate-pulse"
style={{ animationDelay: '1s' }} />
<Skeleton.Base className="h-3 w-36" />
</div>
</div>
{/* Subtle branding hint */}
<div className="pt-8 opacity-50">
<Skeleton.Base className="h-3 w-24 mx-auto" />
</div>
</div>
</div>
);
};
export default OrganizationSkeleton;

View File

@@ -0,0 +1,108 @@
import React from 'react';
import { clsx } from 'clsx';
import { Card } from '@/components/ui/Card';
import { Skeleton } from '@/components/loading/Skeleton';
interface TableSkeletonProps {
rows?: number;
columns?: number;
hasHeader?: boolean;
hasActions?: boolean;
hasAvatar?: boolean;
className?: string;
}
/**
* Loading skeleton for data tables
* Provides flexible column and row configuration with realistic table structure
*/
export const TableSkeleton: React.FC<TableSkeletonProps> = ({
rows = 8,
columns = 5,
hasHeader = true,
hasActions = true,
hasAvatar = false,
className = ''
}) => {
// Adjust effective columns based on avatar and actions
const effectiveColumns = hasActions ? columns : columns;
return (
<Card className={clsx('overflow-hidden', className)}>
{/* Table header */}
{hasHeader && (
<div className="bg-glass-bg border-b border-glass-border px-6 py-4">
<div className={clsx(
'grid gap-4 items-center',
`grid-cols-${Math.min(effectiveColumns, 6)}`
)}>
{Array.from({ length: effectiveColumns }).map((_, index) => (
<Skeleton.Base key={`header-${index}`} className="h-4 w-3/4" />
))}
</div>
</div>
)}
{/* Table rows */}
<div className="divide-y divide-glass-border">
{Array.from({ length: rows }).map((_, rowIndex) => (
<div key={`row-${rowIndex}`} className="px-6 py-4">
<div className={clsx(
'grid gap-4 items-center',
`grid-cols-${Math.min(effectiveColumns, 6)}`
)}>
{Array.from({ length: effectiveColumns }).map((_, colIndex) => {
// First column with optional avatar
if (colIndex === 0) {
return (
<div key={`col-${colIndex}`} className="flex items-center space-x-3">
{hasAvatar && <Skeleton.Avatar size="sm" />}
<Skeleton.Base className="h-4 w-20" />
</div>
);
}
// Last column with actions (if enabled)
if (colIndex === effectiveColumns - 1 && hasActions) {
return (
<div key={`col-${colIndex}`} className="flex space-x-2 justify-end">
<Skeleton.Button size="sm" className="w-16" />
<Skeleton.Button size="sm" className="w-16" />
</div>
);
}
// Regular data columns with varied widths for realism
const widths = ['w-16', 'w-20', 'w-24', 'w-32', 'w-28'];
const widthClass = widths[colIndex % widths.length];
return (
<Skeleton.Base
key={`col-${colIndex}`}
className={`h-4 ${widthClass}`}
/>
);
})}
</div>
</div>
))}
</div>
{/* Table footer (pagination area) */}
<div className="bg-glass-bg border-t border-glass-border px-6 py-4">
<div className="flex items-center justify-between">
<Skeleton.Base className="h-4 w-32" />
<div className="flex space-x-2">
<Skeleton.Button size="sm" className="w-16" />
<Skeleton.Button size="sm" className="w-8" />
<Skeleton.Button size="sm" className="w-8" />
<Skeleton.Button size="sm" className="w-16" />
</div>
</div>
</div>
</Card>
);
};
export default TableSkeleton;

View File

@@ -0,0 +1,25 @@
/**
* Skeleton Components
*
* Regional skeleton components for different UI areas and contexts.
* These replace generic spinners with contextually appropriate loading states.
*/
// Base skeleton components
export {
BaseSkeleton,
TextSkeleton,
AvatarSkeleton,
ButtonSkeleton,
Skeleton
} from '../loading/Skeleton';
export type { SkeletonProps, SkeletonLayoutProps } from '../loading/Skeleton';
// Regional skeleton components
export { EventCardsSkeleton } from './EventCardsSkeleton';
export { KPISkeleton } from './KPISkeleton';
export { TableSkeleton } from './TableSkeleton';
export { FormSkeleton } from './FormSkeleton';
export { OrganizationSkeleton } from './OrganizationSkeleton';
export { LoginSkeleton } from './LoginSkeleton';
export { EventDetailSkeleton } from './EventDetailSkeleton';