Admin: stats privacy toggle, clickable event rows, fix payment method display

- Add useStatsPrivacy hook with localStorage persistence for stats visibility
- Single event page: desktop privacy button, hide capacity chip when stats hidden
- Events list: row/card click navigates to event detail; stopPropagation on actions
- Backend GET /tickets: include payment for each ticket (removes N+1)
- Bookings page: use list payment data, show — when payment method unknown

Made-with: Cursor
This commit is contained in:
Michilis
2026-03-10 01:10:42 +00:00
parent 2f45966932
commit e09ff4ed60
5 changed files with 102 additions and 34 deletions
+13 -6
View File
@@ -41,6 +41,7 @@ import {
} from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';
import clsx from 'clsx';
import { useStatsPrivacy } from '@/hooks/useStatsPrivacy';
type TabType = 'overview' | 'attendees' | 'tickets' | 'email' | 'payments';
@@ -68,7 +69,7 @@ export default function AdminEventDetailPage() {
const [statusFilter, setStatusFilter] = useState<'all' | 'pending' | 'confirmed' | 'checked_in' | 'cancelled'>('all');
const [showAddAtDoorModal, setShowAddAtDoorModal] = useState(false);
const [showManualTicketModal, setShowManualTicketModal] = useState(false);
const [showStats, setShowStats] = useState(true);
const [showStats, setShowStats, toggleStats] = useStatsPrivacy();
const [showNoteModal, setShowNoteModal] = useState(false);
const [selectedTicket, setSelectedTicket] = useState<Ticket | null>(null);
const [noteText, setNoteText] = useState('');
@@ -576,6 +577,10 @@ export default function AdminEventDetailPage() {
</div>
{/* Desktop header actions */}
<div className="hidden md:flex items-center gap-2 flex-shrink-0">
<Button variant="outline" size="sm" onClick={toggleStats} title={showStats ? 'Hide stats' : 'Show stats'}>
{showStats ? <EyeSlashIcon className="w-4 h-4 mr-1.5" /> : <EyeIcon className="w-4 h-4 mr-1.5" />}
{showStats ? 'Hide Stats' : 'Show Stats'}
</Button>
<Link href={`/events/${event.id}`} target="_blank">
<Button variant="outline" size="sm">
<EyeIcon className="w-4 h-4 mr-1.5" />
@@ -606,7 +611,7 @@ export default function AdminEventDetailPage() {
<DropdownItem onClick={() => { router.push(`/admin/events?edit=${event.id}`); setMobileHeaderMenuOpen(false); }}>
<PencilIcon className="w-4 h-4 mr-2" /> Edit Event
</DropdownItem>
<DropdownItem onClick={() => { setShowStats(v => !v); setMobileHeaderMenuOpen(false); }}>
<DropdownItem onClick={() => { toggleStats(); setMobileHeaderMenuOpen(false); }}>
{showStats ? <EyeSlashIcon className="w-4 h-4 mr-2" /> : <EyeIcon className="w-4 h-4 mr-2" />}
{showStats ? 'Hide Stats' : 'Show Stats'}
</DropdownItem>
@@ -628,10 +633,12 @@ export default function AdminEventDetailPage() {
<CurrencyDollarIcon className="w-3.5 h-3.5" />
{event.price === 0 ? 'Free' : formatCurrency(event.price, event.currency)}
</span>
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 bg-gray-100 rounded-full text-xs text-gray-700">
<UsersIcon className="w-3.5 h-3.5" />
{confirmedCount + checkedInCount}/{event.capacity}
</span>
{showStats && (
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 bg-gray-100 rounded-full text-xs text-gray-700">
<UsersIcon className="w-3.5 h-3.5" />
{confirmedCount + checkedInCount}/{event.capacity}
</span>
)}
</div>
{/* ============= STATS ROW ============= */}
+14 -5
View File
@@ -2,7 +2,7 @@
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import { useLanguage } from '@/context/LanguageContext';
import { eventsApi, siteSettingsApi, Event } from '@/lib/api';
import Card from '@/components/ui/Card';
@@ -16,6 +16,7 @@ import toast from 'react-hot-toast';
import clsx from 'clsx';
export default function AdminEventsPage() {
const router = useRouter();
const { t, locale } = useLanguage();
const searchParams = useSearchParams();
const [events, setEvents] = useState<Event[]>([]);
@@ -458,7 +459,11 @@ export default function AdminEventsPage() {
</tr>
) : (
events.map((event) => (
<tr key={event.id} className={clsx("hover:bg-gray-50", featuredEventId === event.id && "bg-amber-50")}>
<tr
key={event.id}
onClick={() => router.push(`/admin/events/${event.id}`)}
className={clsx("hover:bg-gray-50 cursor-pointer", featuredEventId === event.id && "bg-amber-50")}
>
<td className="px-4 py-3">
<div className="flex items-center gap-3">
{event.bannerUrl ? (
@@ -492,7 +497,7 @@ export default function AdminEventsPage() {
)}
</div>
</td>
<td className="px-4 py-3">
<td className="px-4 py-3" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-end gap-1">
{event.status === 'draft' && (
<Button size="sm" variant="ghost" onClick={() => handleStatusChange(event, 'published')}>
@@ -561,7 +566,11 @@ export default function AdminEventsPage() {
</div>
) : (
events.map((event) => (
<Card key={event.id} className={clsx("p-3", featuredEventId === event.id && "ring-2 ring-amber-300")}>
<Card
key={event.id}
className={clsx("p-3 cursor-pointer", featuredEventId === event.id && "ring-2 ring-amber-300")}
onClick={() => router.push(`/admin/events/${event.id}`)}
>
<div className="flex items-start gap-3">
{event.bannerUrl ? (
<img src={event.bannerUrl} alt={event.title}
@@ -590,7 +599,7 @@ export default function AdminEventsPage() {
</div>
<div className="flex items-center justify-between mt-2 pt-2 border-t border-gray-100">
<p className="text-xs text-gray-500">{event.bookedCount || 0} / {event.capacity} spots</p>
<div className="flex items-center gap-1">
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
<Link href={`/admin/events/${event.id}`}
className="p-2 hover:bg-primary-yellow/20 text-primary-dark rounded-btn min-h-[36px] min-w-[36px] flex items-center justify-center">
<EyeIcon className="w-4 h-4" />