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:
@@ -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;
|
||||
174
reactrebuild0825/src/components/skeleton/EventDetailSkeleton.tsx
Normal file
174
reactrebuild0825/src/components/skeleton/EventDetailSkeleton.tsx
Normal 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;
|
||||
122
reactrebuild0825/src/components/skeleton/FormSkeleton.tsx
Normal file
122
reactrebuild0825/src/components/skeleton/FormSkeleton.tsx
Normal 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;
|
||||
55
reactrebuild0825/src/components/skeleton/KPISkeleton.tsx
Normal file
55
reactrebuild0825/src/components/skeleton/KPISkeleton.tsx
Normal 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;
|
||||
117
reactrebuild0825/src/components/skeleton/LoginSkeleton.tsx
Normal file
117
reactrebuild0825/src/components/skeleton/LoginSkeleton.tsx
Normal 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;
|
||||
@@ -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;
|
||||
108
reactrebuild0825/src/components/skeleton/TableSkeleton.tsx
Normal file
108
reactrebuild0825/src/components/skeleton/TableSkeleton.tsx
Normal 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;
|
||||
25
reactrebuild0825/src/components/skeleton/index.ts
Normal file
25
reactrebuild0825/src/components/skeleton/index.ts
Normal 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';
|
||||
Reference in New Issue
Block a user