Compare commits
22 Commits
3dfb1689ad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| defd9685e0 | |||
|
|
22e9254f42 | ||
|
|
2cabd8c92f | ||
|
|
622bb5171c | ||
|
|
55516ef1e7 | ||
| 1ed62b0d3f | |||
| 91de6df04d | |||
| a5d97d65e1 | |||
| f0128f66b0 | |||
| b33c68feb0 | |||
| 15655e3987 | |||
| d8b3864411 | |||
| 194cbd6ca8 | |||
| d5445c2282 | |||
| dcfefc8371 | |||
| b5f14335c4 | |||
| d44ac949b5 | |||
| a5e939221d | |||
| 833e3e5a9c | |||
| ba1975dd6d | |||
| 3025ef3d21 | |||
| 8564f8af83 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,6 +37,8 @@ backend/uploads/
|
|||||||
# Tooling
|
# Tooling
|
||||||
.turbo/
|
.turbo/
|
||||||
.cursor/
|
.cursor/
|
||||||
|
.agents/
|
||||||
|
skills-lock.json
|
||||||
.npm-cache/
|
.npm-cache/
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
|
|||||||
@@ -1476,7 +1476,7 @@ ticketsRouter.post('/admin/guest', requireAuth(['admin', 'organizer', 'staff']),
|
|||||||
attendeePhone: data.phone && data.phone.trim() ? data.phone.trim() : null,
|
attendeePhone: data.phone && data.phone.trim() ? data.phone.trim() : null,
|
||||||
preferredLanguage: data.preferredLanguage || null,
|
preferredLanguage: data.preferredLanguage || null,
|
||||||
status: 'confirmed',
|
status: 'confirmed',
|
||||||
isGuest: true,
|
isGuest: 1,
|
||||||
qrCode,
|
qrCode,
|
||||||
checkinAt: null,
|
checkinAt: null,
|
||||||
adminNote: data.adminNote || null,
|
adminNote: data.adminNote || null,
|
||||||
|
|||||||
@@ -1024,7 +1024,7 @@ export default function AdminEventDetailPage() {
|
|||||||
<td className="px-4 py-2.5">
|
<td className="px-4 py-2.5">
|
||||||
<div className="flex items-center gap-1 flex-wrap">
|
<div className="flex items-center gap-1 flex-wrap">
|
||||||
{getStatusBadge(ticket.status, true)}
|
{getStatusBadge(ticket.status, true)}
|
||||||
{ticket.isGuest && (
|
{!!ticket.isGuest && (
|
||||||
<span className="px-1.5 py-0.5 text-[10px] rounded-full bg-amber-100 text-amber-700 font-medium">Guest</span>
|
<span className="px-1.5 py-0.5 text-[10px] rounded-full bg-amber-100 text-amber-700 font-medium">Guest</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1089,7 +1089,7 @@ export default function AdminEventDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 flex-shrink-0 flex-wrap justify-end">
|
<div className="flex items-center gap-1.5 flex-shrink-0 flex-wrap justify-end">
|
||||||
{getStatusBadge(ticket.status, true)}
|
{getStatusBadge(ticket.status, true)}
|
||||||
{ticket.isGuest && (
|
{!!ticket.isGuest && (
|
||||||
<span className="px-1.5 py-0.5 text-[10px] rounded-full bg-amber-100 text-amber-700 font-medium">Guest</span>
|
<span className="px-1.5 py-0.5 text-[10px] rounded-full bg-amber-100 text-amber-700 font-medium">Guest</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -281,6 +281,22 @@ export default function AdminPaymentsPage() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Hide pending-approval payments whose event has already ended.
|
||||||
|
// Fall back to startDatetime when endDatetime is absent; keep visible when we
|
||||||
|
// can't classify (event missing from list and no startDatetime on payment.event).
|
||||||
|
const visiblePendingApprovalPayments = (() => {
|
||||||
|
const now = new Date();
|
||||||
|
return pendingApprovalPayments.filter((payment) => {
|
||||||
|
const eventId = payment.event?.id;
|
||||||
|
const fullEvent = eventId ? events.find((e) => e.id === eventId) : undefined;
|
||||||
|
const endIso = fullEvent?.endDatetime
|
||||||
|
|| fullEvent?.startDatetime
|
||||||
|
|| payment.event?.startDatetime;
|
||||||
|
if (!endIso) return true;
|
||||||
|
return parseDate(endIso).getTime() >= now.getTime();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
// Get booking info for pending approval payments
|
// Get booking info for pending approval payments
|
||||||
const getPendingBookingInfo = (payment: PaymentWithDetails) => {
|
const getPendingBookingInfo = (payment: PaymentWithDetails) => {
|
||||||
if (!payment.ticket?.bookingId) {
|
if (!payment.ticket?.bookingId) {
|
||||||
@@ -288,7 +304,7 @@ export default function AdminPaymentsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Count all pending payments with the same bookingId
|
// Count all pending payments with the same bookingId
|
||||||
const bookingPayments = pendingApprovalPayments.filter(
|
const bookingPayments = visiblePendingApprovalPayments.filter(
|
||||||
p => p.ticket?.bookingId === payment.ticket?.bookingId
|
p => p.ticket?.bookingId === payment.ticket?.bookingId
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -326,7 +342,7 @@ export default function AdminPaymentsPage() {
|
|||||||
const paidBookingsCount = getUniqueBookingsCount(
|
const paidBookingsCount = getUniqueBookingsCount(
|
||||||
payments.filter(p => p.status === 'paid')
|
payments.filter(p => p.status === 'paid')
|
||||||
);
|
);
|
||||||
const pendingApprovalBookingsCount = getUniqueBookingsCount(pendingApprovalPayments);
|
const pendingApprovalBookingsCount = getUniqueBookingsCount(visiblePendingApprovalPayments);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -620,8 +636,8 @@ export default function AdminPaymentsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">{locale === 'es' ? 'Pendientes de Aprobación' : 'Pending Approval'}</p>
|
<p className="text-sm text-gray-500">{locale === 'es' ? 'Pendientes de Aprobación' : 'Pending Approval'}</p>
|
||||||
<p className="text-xl font-bold text-yellow-600">{pendingApprovalBookingsCount}</p>
|
<p className="text-xl font-bold text-yellow-600">{pendingApprovalBookingsCount}</p>
|
||||||
{pendingApprovalPayments.length !== pendingApprovalBookingsCount && (
|
{visiblePendingApprovalPayments.length !== pendingApprovalBookingsCount && (
|
||||||
<p className="text-xs text-gray-400">({pendingApprovalPayments.length} tickets)</p>
|
<p className="text-xs text-gray-400">({visiblePendingApprovalPayments.length} tickets)</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -670,8 +686,8 @@ export default function AdminPaymentsPage() {
|
|||||||
className={clsx('pb-3 px-1 text-sm font-medium border-b-2 transition-colors whitespace-nowrap min-h-[44px]',
|
className={clsx('pb-3 px-1 text-sm font-medium border-b-2 transition-colors whitespace-nowrap min-h-[44px]',
|
||||||
activeTab === 'pending_approval' ? 'border-primary-yellow text-primary-dark' : 'border-transparent text-gray-500 hover:text-gray-700')}>
|
activeTab === 'pending_approval' ? 'border-primary-yellow text-primary-dark' : 'border-transparent text-gray-500 hover:text-gray-700')}>
|
||||||
{locale === 'es' ? 'Pendientes' : 'Pending Approval'}
|
{locale === 'es' ? 'Pendientes' : 'Pending Approval'}
|
||||||
{pendingApprovalPayments.length > 0 && (
|
{visiblePendingApprovalPayments.length > 0 && (
|
||||||
<span className="ml-2 bg-yellow-100 text-yellow-700 px-2 py-0.5 rounded-full text-xs">{pendingApprovalPayments.length}</span>
|
<span className="ml-2 bg-yellow-100 text-yellow-700 px-2 py-0.5 rounded-full text-xs">{visiblePendingApprovalPayments.length}</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setActiveTab('all')}
|
<button onClick={() => setActiveTab('all')}
|
||||||
@@ -685,7 +701,7 @@ export default function AdminPaymentsPage() {
|
|||||||
{/* Pending Approval Tab */}
|
{/* Pending Approval Tab */}
|
||||||
{activeTab === 'pending_approval' && (
|
{activeTab === 'pending_approval' && (
|
||||||
<>
|
<>
|
||||||
{pendingApprovalPayments.length === 0 ? (
|
{visiblePendingApprovalPayments.length === 0 ? (
|
||||||
<Card className="p-12 text-center">
|
<Card className="p-12 text-center">
|
||||||
<CheckCircleIcon className="w-12 h-12 text-green-400 mx-auto mb-4" />
|
<CheckCircleIcon className="w-12 h-12 text-green-400 mx-auto mb-4" />
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500">
|
||||||
@@ -696,7 +712,7 @@ export default function AdminPaymentsPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{pendingApprovalPayments.map((payment) => {
|
{visiblePendingApprovalPayments.map((payment) => {
|
||||||
const bookingInfo = getPendingBookingInfo(payment);
|
const bookingInfo = getPendingBookingInfo(payment);
|
||||||
return (
|
return (
|
||||||
<Card key={payment.id} className="p-4">
|
<Card key={payment.id} className="p-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user