admin/app/(dashboard)/manage-events/page.tsx
alisaza 82593dc18c refactor(events): update event management components and remove unused files
- Replaced `LIST_PUBLIC_CATEGORIES` with `LIST_ADMIN_CATEGORIES_FLAT` in `ArticleFormModal.tsx`.
- Removed the "Add Event" button from the dashboard page.
- Simplified the `EventsPage` by removing the button and adjusting the layout.
- Cleaned up the `AdminEventEditPage` by removing unnecessary props.
- Deleted unused layout and page files related to event creation.
- Updated `AdminAuthContent` to use `useAdminCitiesQuery` instead of `useDiscoveryCitiesQuery`.
- Refactored `EventGuestListAccessPanel` to remove the `accessMode` prop and adjust API calls accordingly.
- Removed several unused components and tests related to event creation, enhancing project maintainability.
2026-09-07 18:09:55 +03:30

237 lines
6.6 KiB
TypeScript

'use client'
import { useEffect, useMemo, useState } from 'react'
import type { PaginationListColumnType } from '@/types'
import PaginatedList from '@/components/PaginatedList'
import PageNavbar from '@/components/layouts/PageNavbar'
import AdminTableViewButton from '@/components/ui/AdminTableViewButton'
import StatusChip from '@/components/ui/StatusChip'
import axiosInstance from '@/config/axios'
import { APP_ROUTES } from '@/constants/routes'
import { EVENT_STATUS_FILTER_ITEMS, getBooleanStatus, getEventStatus } from '@/constants/status'
import { formatCurrency, formatPersonName, coerceToString } from '@/helpers'
import { formatPersianDate } from '@/lib/formatters'
import { API_ROUTES } from '@/services/config'
interface NamedEntity {
id: number | string
name: string
}
interface EventOrganizer {
id: string
firstName: string | null
lastName: string | null
}
interface EventRow {
id: string
title: string
status: string
isDiscoverable: boolean
isFree: boolean
price: number
category?: NamedEntity
province?: NamedEntity
city?: NamedEntity
organizer?: EventOrganizer
[key: string]: unknown
}
interface FilterOption {
code: string
name: string
}
const IS_FREE_FILTER_ITEMS: FilterOption[] = [
{ code: 'true', name: 'بله' },
{ code: 'false', name: 'خیر' },
]
const IS_DISCOVERABLE_FILTER_ITEMS: FilterOption[] = [
{ code: 'true', name: 'بله' },
{ code: 'false', name: 'خیر' },
]
const toSelectFilterItems = (items: NamedEntity[]): FilterOption[] => items.map((item) => ({ code: String(item.id), name: item.name }))
const extractApiList = <T,>(raw: unknown): T[] => {
if (Array.isArray(raw)) return raw as T[]
if (typeof raw === 'object' && raw !== null && 'data' in raw) {
const data = raw.data
if (Array.isArray(data)) return data as T[]
}
return []
}
const getOrganizer = (row: EventRow): EventOrganizer => {
if (row.organizer && typeof row.organizer === 'object') {
return row.organizer
}
return { id: '—', firstName: null, lastName: null }
}
const EventsPage = () => {
const [categoryFilterItems, setCategoryFilterItems] = useState<FilterOption[]>([])
const [cityFilterItems, setCityFilterItems] = useState<FilterOption[]>([])
useEffect(() => {
const loadFilterOptions = async () => {
try {
const [categoriesRes, citiesRes] = await Promise.all([
axiosInstance.get(API_ROUTES.EVENT_CATEGORIES.ADMIN_FLAT),
axiosInstance.get(API_ROUTES.GEOGRAPHY.ALL_CITIES),
])
setCategoryFilterItems(toSelectFilterItems(extractApiList<NamedEntity>(categoriesRes.data)))
setCityFilterItems(toSelectFilterItems(extractApiList<NamedEntity>(citiesRes.data)))
} catch {
setCategoryFilterItems([])
setCityFilterItems([])
}
}
void loadFilterOptions()
}, [])
const columns = useMemo<PaginationListColumnType[]>(
() => [
{
field: 'title',
label: 'عنوان',
filterable: true,
type: 'text',
sortable: false,
},
{
field: 'status',
label: 'وضعیت',
filterable: true,
type: 'select',
sortable: false,
filterItems: EVENT_STATUS_FILTER_ITEMS,
},
{
field: 'isDiscoverable',
label: 'قابل‌جستجو',
filterable: true,
type: 'select',
sortable: false,
filterItems: IS_DISCOVERABLE_FILTER_ITEMS,
},
{
field: 'categoryId',
label: 'دسته‌بندی',
filterable: true,
type: 'select',
sortable: false,
filterItems: categoryFilterItems,
},
{
field: 'organizerId',
label: 'برگزارکننده',
filterable: true,
type: 'text',
sortable: false,
},
// Province-level filtering is disabled here too — see cities/page.tsx's
// note for why (city-only admin surface). Only the city filter below
// is active.
{
field: 'cityId',
label: 'شهر',
filterable: true,
type: 'select',
sortable: false,
filterItems: cityFilterItems,
},
{
field: 'startsAt',
label: 'تاریخ شروع',
filterable: true,
sortable: true,
type: 'dateFromTo',
},
{
field: 'isFree',
label: 'رایگان',
filterable: true,
type: 'select',
sortable: false,
filterItems: IS_FREE_FILTER_ITEMS,
},
{
field: 'price',
label: 'قیمت',
filterable: true,
type: 'inputFromTo',
sortable: false,
},
{
field: 'actions',
label: 'عملیات',
},
],
[categoryFilterItems, cityFilterItems]
)
return (
<section className="h-full w-full text-right">
<PageNavbar pageTitle="رویدادها" />
<div className="admin-page-container">
<PaginatedList
columns={columns}
url={API_ROUTES.EVENTS.ADMIN_LIST}
>
{{
status: (_row, cellValue) => <StatusChip {...getEventStatus(coerceToString(cellValue))} />,
isDiscoverable: (_row, cellValue) => <StatusChip {...getBooleanStatus(typeof cellValue === 'boolean' ? cellValue : null)} />,
categoryId: (row) => {
const event = row as EventRow
return event.category?.name ?? '—'
},
organizerId: (row) => {
const organizer = getOrganizer(row as EventRow)
return formatPersonName(organizer.firstName, organizer.lastName)
},
cityId: (row) => {
const event = row as EventRow
return event.city?.name ?? '—'
},
startsAt: (_row, cellValue) => formatPersianDate(cellValue),
isFree: (_row, cellValue) => <StatusChip {...getBooleanStatus(typeof cellValue === 'boolean' ? cellValue : null)} />,
price: (row, cellValue) => {
const event = row as EventRow
if (event.isFree) return '—'
const amount = typeof cellValue === 'number' ? cellValue : Number(cellValue)
if (!Number.isFinite(amount)) return '—'
return formatCurrency(amount)
},
actions: (row) => (
<AdminTableViewButton
label="مشاهده رویداد"
mode="navigate"
to={APP_ROUTES.MANAGE_EVENT_DETAIL(String((row as EventRow).id))}
/>
),
}}
</PaginatedList>
</div>
</section>
)
}
export default EventsPage