- Replace the three Attendees-tab modals (Manual Ticket / Add at Door / Invite Guest) with a single Add Ticket modal: Paid/Unpaid/Guest segmented control, shared fields, "Check in now" for all types, and a live "what happens" preview, backed by one POST /api/tickets/admin/add. - Add tickets.payment_status (paid | unpaid | comp) with a backfill migration; keep it in sync on every payment-settlement path (mark-paid, admin approval, Lightning, free bookings, hold recovery). - Show Paid/Unpaid/Comp badges in the attendee list, count only paid tickets toward revenue, let unpaid tickets be resolved via Mark Paid, and flag unpaid tickets with their balance due in the door scanner. - Replace the per-page useStatsPrivacy hook with an admin-wide PrivacyContext + SensitiveValue mask, toggled from the admin layout. - Add server-side pagination with page-size options to the users page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.6 KiB
TypeScript
85 lines
3.6 KiB
TypeScript
import { Event } from '@/lib/api';
|
|
import Card from '@/components/ui/Card';
|
|
import SensitiveValue from '@/components/admin/SensitiveValue';
|
|
import { CalendarIcon, MapPinIcon, CurrencyDollarIcon, UsersIcon } from '@heroicons/react/24/outline';
|
|
|
|
interface OverviewTabProps {
|
|
event: Event;
|
|
formatDate: (dateStr: string) => string;
|
|
fmtTime: (dateStr: string) => string;
|
|
formatCurrency: (amount: number, currency: string) => string;
|
|
confirmedCount: number;
|
|
checkedInCount: number;
|
|
}
|
|
|
|
export function OverviewTab({ event, formatDate, fmtTime, formatCurrency, confirmedCount, checkedInCount }: OverviewTabProps) {
|
|
return (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<Card className="p-5">
|
|
<h3 className="font-semibold text-base mb-3">Event Information</h3>
|
|
<div className="space-y-3">
|
|
<div className="flex items-start gap-3">
|
|
<CalendarIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
|
<div>
|
|
<p className="font-medium text-sm">Date & Time</p>
|
|
<p className="text-sm text-gray-600">{formatDate(event.startDatetime)}</p>
|
|
<p className="text-sm text-gray-600">{fmtTime(event.startDatetime)}{event.endDatetime && ` - ${fmtTime(event.endDatetime)}`}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start gap-3">
|
|
<MapPinIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
|
<div>
|
|
<p className="font-medium text-sm">Location</p>
|
|
<p className="text-sm text-gray-600">{event.location}</p>
|
|
{event.locationUrl && (
|
|
<a href={event.locationUrl} target="_blank" rel="noopener" className="text-blue-600 text-xs hover:underline">
|
|
View on Map
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start gap-3">
|
|
<CurrencyDollarIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
|
<div>
|
|
<p className="font-medium text-sm">Price</p>
|
|
<p className="text-sm text-gray-600">{event.price === 0 ? 'Free' : formatCurrency(event.price, event.currency)}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start gap-3">
|
|
<UsersIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
|
<div>
|
|
<p className="font-medium text-sm">Capacity</p>
|
|
<p className="text-sm text-gray-600"><SensitiveValue>{confirmedCount + checkedInCount} / {event.capacity}</SensitiveValue> spots filled</p>
|
|
<p className="text-xs text-gray-500"><SensitiveValue>{Math.max(0, event.capacity - confirmedCount - checkedInCount)}</SensitiveValue> spots remaining</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
<Card className="p-5">
|
|
<h3 className="font-semibold text-base mb-3">Description</h3>
|
|
<div className="prose prose-sm max-w-none">
|
|
<p className="text-sm text-gray-600 whitespace-pre-wrap">{event.description}</p>
|
|
{event.descriptionEs && (
|
|
<>
|
|
<p className="font-medium text-sm mt-3">Spanish:</p>
|
|
<p className="text-sm text-gray-600 whitespace-pre-wrap">{event.descriptionEs}</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
|
|
{event.bannerUrl && (
|
|
<Card className="p-5 lg:col-span-2">
|
|
<h3 className="font-semibold text-base mb-3">Event Banner</h3>
|
|
<img
|
|
src={event.bannerUrl}
|
|
alt={event.title}
|
|
className="w-full max-h-64 object-cover rounded-lg"
|
|
/>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|