admin/app/(dashboard)/bookings/page.tsx
alisaza 3a08625cbe feat(bookings): add cancellation reason column to bookings table
Enhanced the bookings page and admin event bookings tab by adding a new column for 'cancellationReason'. This column displays the reason for cancellations when applicable, improving the visibility of booking statuses. Additionally, removed the 'غایب' (no-show) insight stat from the event overview tab for a cleaner presentation of event insights.
2026-09-11 22:56:04 +03:30

168 lines
4.5 KiB
TypeScript

'use client'
import type { PaginationListColumnType } from '@/types'
import PaginatedList from '@/components/PaginatedList'
import StatusChip from '@/components/ui/StatusChip'
import PageNavbar from '@/components/layouts/PageNavbar'
import AdminTableViewButton from '@/components/ui/AdminTableViewButton'
import { APP_ROUTES } from '@/constants/routes'
import { formatPersonName, coerceToString } from '@/helpers'
import { formatIranianMobile, formatPersianDate } from '@/lib/formatters'
import { BOOKING_STATUS_FILTER_ITEMS, getBookingStatus } from '@/constants/status'
import { API_ROUTES } from '@/services/config'
interface BookingUser {
id: string
mobile: string
firstName: string | null
lastName: string | null
}
interface BookingEvent {
id: string
title: string
startsAt: string
status: string
organizerId: string
}
const columns: PaginationListColumnType[] = [
{
field: 'bookingCode',
label: 'کد رزرو',
filterable: false,
sortable: false,
type: 'text',
},
{
field: 'userId',
label: 'مهمان',
filterable: true,
sortable: false,
type: 'text',
},
{
field: 'eventId',
label: 'رویداد',
filterable: true,
sortable: false,
type: 'text',
},
{
field: 'status',
label: 'وضعیت',
filterable: true,
sortable: true,
type: 'select',
filterItems: BOOKING_STATUS_FILTER_ITEMS,
},
{
field: 'cancellationReason',
label: 'دلیل لغو',
filterable: false,
sortable: false,
type: 'text',
},
{
field: 'checkedInAt',
label: 'چک‌این',
filterable: false,
sortable: false,
type: 'date',
},
{
field: 'createdAt',
label: 'تاریخ ثبت',
filterable: false,
sortable: true,
type: 'date',
},
{
field: 'actions',
label: 'عملیات',
},
]
// Platform-wide, read-only admin bookings list. No write actions here by
// design — cancel/check-in/refund stay on their existing flows; the view
// button jumps to the guest's user-detail page (رزروها tab).
const BookingsPage = () => {
return (
<section className="h-full w-full text-right">
<PageNavbar pageTitle="رزروها" />
<div className="admin-page-container">
<PaginatedList
columns={columns}
url={API_ROUTES.BOOKINGS.ADMIN_LIST}
>
{{
userId: (row) => {
const user = row.user as BookingUser | undefined
if (!user) return '—'
const name = formatPersonName(user.firstName ?? undefined, user.lastName ?? undefined)
return (
<div className="flex flex-col gap-1">
<span>{name}</span>
<span
className="text-xs text-tertiary-300"
dir="ltr"
>
{formatIranianMobile(user.mobile)}
</span>
</div>
)
},
eventId: (row) => {
const event = row.event as BookingEvent | undefined
return event?.title ?? '—'
},
status: (_row, cellValue) => {
const { label, chipColor } = getBookingStatus(coerceToString(cellValue))
return (
<StatusChip
chipColor={chipColor}
label={label}
/>
)
},
cancellationReason: (row, cellValue) => {
const reason = coerceToString(cellValue).trim()
const isCancelledLike = row.status === 'cancelled' || row.status === 'refunded'
if (!isCancelledLike || !reason) return '—'
return (
<span className="max-w-[220px] whitespace-pre-wrap break-words text-xs text-secondary-20">
{reason}
</span>
)
},
checkedInAt: (_row, cellValue) => formatPersianDate(cellValue),
createdAt: (_row, cellValue) => formatPersianDate(cellValue),
actions: (row) => {
const user = row.user as BookingUser | undefined
if (!user) return '—'
return (
<AdminTableViewButton
label="مشاهده کاربر"
mode="navigate"
to={APP_ROUTES.USER_DETAIL(user.id)}
/>
)
},
}}
</PaginatedList>
</div>
</section>
)
}
export default BookingsPage