admin/app/(dashboard)/reviews/page.tsx
alisaza 96603d31f7 feat(alert-modal): integrate alert modal for confirmation actions
Enhanced various components to utilize the alert modal for user confirmations before executing critical actions. This includes marking messages as read, saving notification rules, restoring reviews, sending replies, changing ticket statuses, and managing event commissions. The integration improves user experience by ensuring actions are intentional and provides clear feedback on the outcomes.
2026-09-13 10:14:18 +03:30

230 lines
7.0 KiB
TypeScript

'use client'
import type { PaginationListColumnType } from '@/types'
import PaginatedList from '@/components/PaginatedList'
import PageNavbar from '@/components/layouts/PageNavbar'
import Button from '@/components/formElements/Button'
import EyeCrossedIcon from '@/components/icons/EyeCrossedIcon'
import FileCheckIcon from '@/components/icons/FileCheckIcon'
import TrashIcon from '@/components/icons/TrashIcon'
import AdminTableActions from '@/components/ui/AdminTableActions'
import AdminTableViewButton from '@/components/ui/AdminTableViewButton'
import StatusChip from '@/components/ui/StatusChip'
import axiosInstance from '@/config/axios'
import { APP_ROUTES } from '@/constants/routes'
import useAlertModal from '@/hooks/useAlertModal'
import useAdminMutation from '@/hooks/useAdminMutation'
import { formatPersonName, coerceToString } from '@/helpers'
import { formatPersianDate, truncateValue } from '@/lib/formatters'
import { getReviewStatus, REVIEW_STATUS_FILTER_ITEMS, type ReviewStatus } from '@/constants/status'
import { API_ROUTES } from '@/services/config'
const columns: PaginationListColumnType[] = [
{
field: 'eventId',
label: 'رویداد',
filterable: true,
type: 'text',
sortable: false,
},
{
field: 'userId',
label: 'نویسنده',
filterable: false,
sortable: false,
},
{
field: 'rating',
label: 'امتیاز',
filterable: true,
type: 'number',
sortable: true,
},
{
field: 'status',
label: 'وضعیت',
filterable: true,
type: 'select',
sortable: false,
filterItems: REVIEW_STATUS_FILTER_ITEMS,
},
{
field: 'createdAt',
label: 'تاریخ ثبت',
filterable: true,
sortable: true,
type: 'dateFromTo',
},
{
field: 'body',
label: 'متن نظر',
filterable: false,
sortable: false,
},
{
field: 'actions',
label: 'عملیات',
},
]
interface ReviewEventSummary {
id: string
title: string
}
interface ReviewUserSummary {
id: string
firstName: string | null
lastName: string | null
}
interface ReviewRow {
id: string
eventId: string
userId: string
status: ReviewStatus
event?: ReviewEventSummary
user?: ReviewUserSummary
[key: string]: unknown
}
const getEventSummary = (row: ReviewRow) => {
if (row.event && typeof row.event === 'object') {
return row.event
}
return { id: row.eventId, title: '—' }
}
const getUserSummary = (row: ReviewRow) => {
if (row.user && typeof row.user === 'object') {
return row.user
}
return { id: row.userId, firstName: null, lastName: null }
}
const ReviewsPage = () => {
const { showAlert } = useAlertModal()
const { pendingId, runAction } = useAdminMutation({ url: API_ROUTES.REVIEWS.ADMIN_LIST })
const handleHide = (row: ReviewRow) => {
showAlert('این نظر از نمایش عمومی مخفی شود؟', () =>
runAction(row.id, () => axiosInstance.patch(API_ROUTES.REVIEWS.ADMIN_HIDE(row.id)), 'نظر مخفی شد')
)
}
const handleRestore = (row: ReviewRow) => {
showAlert('این نظر دوباره در نمایش عمومی قرار گیرد؟', () =>
runAction(row.id, () => axiosInstance.patch(API_ROUTES.REVIEWS.ADMIN_RESTORE(row.id)), 'نظر بازگردانده شد')
)
}
const handleDelete = (row: ReviewRow) => {
showAlert(
'این نظر برای همیشه حذف شود؟ این عملیات قابل بازگشت نیست.',
() => runAction(row.id, () => axiosInstance.delete(API_ROUTES.REVIEWS.ADMIN_DELETE(row.id)), 'نظر حذف شد'),
undefined,
{ dangerAccept: true }
)
}
return (
<section className="h-full w-full text-right">
<PageNavbar pageTitle="مدیریت نظرات" />
<div className="admin-page-container">
<PaginatedList
columns={columns}
url={API_ROUTES.REVIEWS.ADMIN_LIST}
>
{{
eventId: (row) => getEventSummary(row as ReviewRow).title,
userId: (row) => {
const user = getUserSummary(row as ReviewRow)
return formatPersonName(user.firstName, user.lastName)
},
body: (_row, cellValue) => truncateValue(cellValue),
status: (_row, cellValue) => {
const { label, chipColor } = getReviewStatus(coerceToString(cellValue))
return (
<StatusChip
chipColor={chipColor}
label={label}
/>
)
},
createdAt: (_row, cellValue) => formatPersianDate(cellValue),
actions: (row) => {
const review = row as ReviewRow
const isBusy = pendingId === review.id
const event = getEventSummary(review)
return (
<AdminTableActions>
<AdminTableViewButton
label="مشاهده رویداد"
mode="navigate"
to={APP_ROUTES.MANAGE_EVENT_DETAIL(event.id)}
/>
{review.status !== 'deleted' && review.status === 'published' ? (
<Button
iconOnly
aria-label="مخفی کردن نظر"
color="warning"
disabled={isBusy}
isLoading={isBusy}
size="sm"
variant="flat"
onClick={() => {
handleHide(review)
}}
>
<EyeCrossedIcon className="size-4" />
</Button>
) : null}
{review.status !== 'deleted' && review.status === 'hidden' ? (
<Button
iconOnly
aria-label="بازگردانی نظر"
color="success"
disabled={isBusy}
isLoading={isBusy}
size="sm"
variant="flat"
onClick={() => {
handleRestore(review)
}}
>
<FileCheckIcon className="size-4" />
</Button>
) : null}
{review.status !== 'deleted' ? (
<Button
iconOnly
aria-label="حذف نظر"
color="danger"
disabled={isBusy}
isLoading={isBusy}
size="sm"
variant="flat"
onClick={() => {
handleDelete(review)
}}
>
<TrashIcon className="size-4 text-fourth-900" />
</Button>
) : null}
</AdminTableActions>
)
},
}}
</PaginatedList>
</div>
</section>
)
}
export default ReviewsPage