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.
230 lines
7.0 KiB
TypeScript
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
|