admin/app/(dashboard)/bookings/page.tsx
alisaza 0a68104e85 chore(openapi): format tags in API documentation for consistency
Updated the OpenAPI specification to format the 'tags' property as an array on multiple endpoints, ensuring consistent styling across the documentation. Additionally, simplified JSX return statements in the BookingsPage and AdminEventBookingsTab components for improved readability.
2026-09-11 23:00:12 +03:30

164 lines
4.4 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