Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
170 lines
5.1 KiB
TypeScript
170 lines
5.1 KiB
TypeScript
import type { CreateEventCategoryDto, EventCategoryResponseDto } from '@/api/generated/models'
|
|
import {
|
|
createServiceError,
|
|
errorResult,
|
|
handleServiceError,
|
|
type ServiceCallOptions,
|
|
type ServiceResult,
|
|
shouldBubbleErrorToParent,
|
|
successResult,
|
|
} from '@/services/errorHandler'
|
|
import { unwrapApiData } from '@/services/apiResponse'
|
|
import { getAdminEventCategories } from '@/api/generated/admin-event-categories/admin-event-categories'
|
|
import { getEventCategories } from '@/api/generated/event-categories/event-categories'
|
|
import { texts } from '@/texts'
|
|
|
|
export type EventCategory = EventCategoryResponseDto
|
|
export type EventCategoryPayload = CreateEventCategoryDto
|
|
|
|
const eventCategoriesApi = getEventCategories()
|
|
const adminEventCategoriesApi = getAdminEventCategories()
|
|
|
|
/**
|
|
* Public, unpaginated flat list of *active* categories — `GET /event-categories`
|
|
* (see backend/src/modules/event-categories/event-categories.controller.ts).
|
|
* Used by the consumer app's categories tab; unlike GET_CATEGORY_CHILDREN
|
|
* below, this returns every active category in one call (parentId included
|
|
* per row), so the caller builds the tree client-side instead of fetching
|
|
* level-by-level.
|
|
*/
|
|
export const LIST_PUBLIC_CATEGORIES = async (options?: ServiceCallOptions): Promise<ServiceResult<{ items: EventCategory[] }>> => {
|
|
try {
|
|
const res = await eventCategoriesApi.eventCategoriesControllerList()
|
|
const payload = res.data
|
|
|
|
if (!payload.success) {
|
|
throw createServiceError(payload.message || texts.events.categoriesListFailed)
|
|
}
|
|
|
|
const items = unwrapApiData(payload)
|
|
|
|
return successResult({ items: items ?? [] })
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) {
|
|
throw normalizedError
|
|
}
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* One "level" of the category tree: every direct child of `parentId`
|
|
* (or every root category, when `parentId` is null). Page size is fixed
|
|
* at the backend's max (100) — there's no pagination UI within a tree
|
|
* level; if an organizer ever needs more than 100 siblings under one
|
|
* parent, that's a real product problem, not something to paginate away.
|
|
*/
|
|
export const GET_CATEGORY_CHILDREN = async (
|
|
parentId: number | null,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<EventCategory[]>> => {
|
|
try {
|
|
const res = await adminEventCategoriesApi.adminEventCategoriesControllerList({
|
|
pageSize: 100,
|
|
filters: { parentId: parentId === null ? 'null' : String(parentId) },
|
|
})
|
|
const payload = res.data
|
|
|
|
if (!payload.success) {
|
|
throw createServiceError(payload.message || texts.events.categoriesListFailed)
|
|
}
|
|
|
|
const list = unwrapApiData(payload)
|
|
|
|
return successResult(list?.items ?? [])
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) {
|
|
throw normalizedError
|
|
}
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const CREATE_CATEGORY = async (
|
|
payload: EventCategoryPayload,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<EventCategory>> => {
|
|
try {
|
|
const res = await adminEventCategoriesApi.adminEventCategoriesControllerCreate(payload)
|
|
const body = res.data
|
|
|
|
if (!body.success) {
|
|
throw createServiceError(body.message || texts.events.categoryCreateFailed)
|
|
}
|
|
|
|
const category = unwrapApiData(body)
|
|
|
|
if (!category?.id) {
|
|
throw createServiceError(texts.common.invalidServerResponse)
|
|
}
|
|
|
|
return successResult(category)
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) {
|
|
throw normalizedError
|
|
}
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const UPDATE_CATEGORY = async (
|
|
id: number,
|
|
payload: Omit<EventCategoryPayload, 'parentId'>,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<EventCategory>> => {
|
|
try {
|
|
const res = await adminEventCategoriesApi.adminEventCategoriesControllerUpdate(id, payload)
|
|
const body = res.data
|
|
|
|
if (!body.success) {
|
|
throw createServiceError(body.message || texts.events.categoryUpdateFailed)
|
|
}
|
|
|
|
const category = unwrapApiData(body)
|
|
|
|
if (!category?.id) {
|
|
throw createServiceError(texts.common.invalidServerResponse)
|
|
}
|
|
|
|
return successResult(category)
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) {
|
|
throw normalizedError
|
|
}
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const DELETE_CATEGORY = async (id: number, options?: ServiceCallOptions): Promise<ServiceResult<{ success: boolean }>> => {
|
|
try {
|
|
const res = await adminEventCategoriesApi.adminEventCategoriesControllerRemove(id)
|
|
const body = res.data
|
|
|
|
if (!body.success) {
|
|
throw createServiceError(body.message || texts.events.categoryDeleteFailed)
|
|
}
|
|
|
|
return successResult(unwrapApiData(body) ?? { success: true })
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) {
|
|
throw normalizedError
|
|
}
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|