Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
228 lines
6.8 KiB
TypeScript
228 lines
6.8 KiB
TypeScript
import type {
|
|
AdminReviewResponseDto,
|
|
CreateReviewDto,
|
|
HostReplyDto,
|
|
OrganizerReviewResponseDto,
|
|
ReviewResponseDto,
|
|
UpdateReviewDto,
|
|
} from '@/api/generated/models'
|
|
import { getAdminReviews } from '@/api/generated/admin-reviews/admin-reviews'
|
|
import { getReviews } from '@/api/generated/reviews/reviews'
|
|
import { unwrapApiPayload } from '@/helpers/listResponse'
|
|
import { parseRemittanceList } from '@/helpers/listResponse'
|
|
import {
|
|
errorResult,
|
|
handleServiceError,
|
|
type ServiceCallOptions,
|
|
type ServiceResult,
|
|
shouldBubbleErrorToParent,
|
|
successResult,
|
|
} from '@/services/errorHandler'
|
|
|
|
export type EventReview = ReviewResponseDto
|
|
export type AdminEventReview = AdminReviewResponseDto
|
|
export type OrganizerReview = OrganizerReviewResponseDto
|
|
|
|
const reviewsApi = getReviews()
|
|
const adminReviewsApi = getAdminReviews()
|
|
|
|
export const EVENT_REVIEWS_PAGE_SIZE = 10
|
|
export const ORGANIZER_REVIEWS_PAGE_SIZE = 10
|
|
|
|
interface PaginatedReviews<T> {
|
|
items: T[]
|
|
response?: { totalItemsCount?: number; totalPages?: number; page?: number }
|
|
}
|
|
|
|
const unwrapItems = <T>(payload: unknown): T[] => {
|
|
if (Array.isArray(payload)) return payload as T[]
|
|
if (payload && typeof payload === 'object' && Array.isArray((payload as PaginatedReviews<T>).items)) {
|
|
return (payload as PaginatedReviews<T>).items
|
|
}
|
|
|
|
return []
|
|
}
|
|
|
|
export const LIST_EVENT_REVIEWS_PAGE = async (
|
|
eventId: string,
|
|
page = 1,
|
|
pageSize = EVENT_REVIEWS_PAGE_SIZE,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<{ items: EventReview[]; page: number; totalItemsCount: number; totalPages: number }>> => {
|
|
try {
|
|
const res = await reviewsApi.reviewsControllerListByEvent(eventId, {
|
|
sort: '-createdAt',
|
|
page,
|
|
pageSize,
|
|
})
|
|
const parsed = parseRemittanceList<EventReview>(res.data, 'items', page, pageSize)
|
|
|
|
return successResult({
|
|
items: parsed.items,
|
|
page: parsed.pagination.page,
|
|
totalItemsCount: parsed.pagination.totalItemsCount,
|
|
totalPages: parsed.pagination.totalPages ?? 0,
|
|
})
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) throw normalizedError
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
/** Admin list for one event — includes published, hidden, and deleted reviews. */
|
|
export const LIST_ADMIN_EVENT_REVIEWS_PAGE = async (
|
|
eventId: string,
|
|
page = 1,
|
|
pageSize = EVENT_REVIEWS_PAGE_SIZE,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<{ items: AdminEventReview[]; page: number; totalItemsCount: number; totalPages: number }>> => {
|
|
try {
|
|
const res = await adminReviewsApi.adminReviewsControllerList({
|
|
sort: '-createdAt',
|
|
page,
|
|
pageSize,
|
|
'filters[eventId]': eventId,
|
|
})
|
|
const parsed = parseRemittanceList<AdminEventReview>(res.data, 'items', page, pageSize)
|
|
|
|
return successResult({
|
|
items: parsed.items,
|
|
page: parsed.pagination.page,
|
|
totalItemsCount: parsed.pagination.totalItemsCount,
|
|
totalPages: parsed.pagination.totalPages ?? 0,
|
|
})
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) throw normalizedError
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const LIST_EVENT_REVIEWS = async (eventId: string, options?: ServiceCallOptions): Promise<ServiceResult<EventReview[]>> => {
|
|
const result = await LIST_EVENT_REVIEWS_PAGE(eventId, 1, 50, options)
|
|
|
|
return result.ok ? successResult(result.data.items) : result
|
|
}
|
|
|
|
export const LIST_ORGANIZER_REVIEWS = async (
|
|
organizerId: string,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<OrganizerReview[]>> => {
|
|
try {
|
|
const res = await reviewsApi.reviewsControllerListByOrganizer(organizerId, {
|
|
sort: '-createdAt',
|
|
page: 1,
|
|
pageSize: 50,
|
|
})
|
|
|
|
return successResult(unwrapItems<OrganizerReview>(unwrapApiPayload(res.data)))
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) throw normalizedError
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const LIST_ORGANIZER_REVIEWS_PAGE = async (
|
|
organizerId: string,
|
|
page = 1,
|
|
pageSize = ORGANIZER_REVIEWS_PAGE_SIZE,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<{ items: OrganizerReview[]; page: number; totalItemsCount: number; totalPages: number }>> => {
|
|
try {
|
|
const res = await reviewsApi.reviewsControllerListByOrganizer(organizerId, {
|
|
sort: '-createdAt',
|
|
page,
|
|
pageSize,
|
|
})
|
|
const parsed = parseRemittanceList<OrganizerReview>(res.data, 'items', page, pageSize)
|
|
|
|
return successResult({
|
|
items: parsed.items,
|
|
page: parsed.pagination.page,
|
|
totalItemsCount: parsed.pagination.totalItemsCount,
|
|
totalPages: parsed.pagination.totalPages ?? 0,
|
|
})
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) throw normalizedError
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
/** Published review by this user for the event, if any (from the public event list). */
|
|
export const FIND_MY_EVENT_REVIEW = async (
|
|
eventId: string,
|
|
userId: string,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<EventReview | null>> => {
|
|
const result = await LIST_EVENT_REVIEWS(eventId, options)
|
|
|
|
if (!result.ok) return result
|
|
|
|
return successResult(result.data.find((review) => review.userId === userId) ?? null)
|
|
}
|
|
|
|
export const CREATE_REVIEW = async (
|
|
eventId: string,
|
|
dto: CreateReviewDto,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<EventReview>> => {
|
|
try {
|
|
const res = await reviewsApi.reviewsControllerCreate(eventId, dto)
|
|
|
|
return successResult(unwrapApiPayload(res.data))
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) throw normalizedError
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const UPDATE_REVIEW = async (
|
|
reviewId: string,
|
|
dto: UpdateReviewDto,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<EventReview>> => {
|
|
try {
|
|
const res = await reviewsApi.reviewsControllerUpdate(reviewId, dto)
|
|
|
|
return successResult(unwrapApiPayload(res.data))
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) throw normalizedError
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const HOST_REPLY_REVIEW = async (
|
|
reviewId: string,
|
|
dto: HostReplyDto,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<EventReview>> => {
|
|
try {
|
|
const res = await reviewsApi.reviewsControllerHostReply(reviewId, dto)
|
|
|
|
return successResult(unwrapApiPayload(res.data))
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) throw normalizedError
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|