Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
123 lines
4.4 KiB
TypeScript
123 lines
4.4 KiB
TypeScript
import type { ApiWithParams, PaginationListColumnType } from '@/types'
|
|
import { removeEmptyFields, coerceToString } from '@/helpers'
|
|
|
|
export const ALL_FILTER_OPTION_CODE = '__all__'
|
|
|
|
export type PaginatedListFilterValue = string | number | { from?: string; to?: string }
|
|
export type PaginatedListFilters = Record<string, PaginatedListFilterValue>
|
|
export type PaginatedListRow = Record<string, unknown> & { id?: string | number }
|
|
|
|
/** Which filter control a column renders. The single source of truth for mapping `column.type` to filter behavior — both the inline table-header filter and the modal accordion filter dispatch off of this instead of each re-implementing the mapping. */
|
|
export type FilterFieldKind = 'scalar' | 'range' | 'select' | 'date' | 'dateRange' | 'none'
|
|
|
|
export function getColumnFilterConfig(column: PaginationListColumnType): FilterFieldKind {
|
|
if (!column.type || column.type === 'text' || column.type === 'number') return 'scalar'
|
|
if (column.type === 'inputFromTo') return 'range'
|
|
if (column.type === 'select') return column.filterItems?.length ? 'select' : 'none'
|
|
if (column.type === 'date') return 'date'
|
|
if (column.type === 'dateFromTo') return 'dateRange'
|
|
|
|
return 'none'
|
|
}
|
|
|
|
export function toDatePickerValue(value?: string): Date | null {
|
|
if (!value) return null
|
|
const date = new Date(value)
|
|
|
|
return Number.isFinite(date.getTime()) ? date : null
|
|
}
|
|
|
|
export function toSelectFilterSelectedKeys(value: unknown): string[] {
|
|
return value == null || value === '' ? [ALL_FILTER_OPTION_CODE] : [coerceToString(value)]
|
|
}
|
|
|
|
export function withAllFilterOption(items: { code: string; name: string }[]) {
|
|
return items.some((item) => item.code === ALL_FILTER_OPTION_CODE) ? items : [{ code: ALL_FILTER_OPTION_CODE, name: 'همه' }, ...items]
|
|
}
|
|
|
|
export function isAllFilterValue(value: string): boolean {
|
|
return value === ALL_FILTER_OPTION_CODE || value === ''
|
|
}
|
|
|
|
export function getFilterTextValue(value?: PaginatedListFilterValue): string {
|
|
return value == null || typeof value === 'object' ? '' : coerceToString(value)
|
|
}
|
|
|
|
export function getNestedFilterValue(value: PaginatedListFilterValue | undefined, field: 'from' | 'to'): string | undefined {
|
|
return value && typeof value === 'object' ? value[field] : undefined
|
|
}
|
|
|
|
export function getFilterDateValue(value?: PaginatedListFilterValue): string | undefined {
|
|
return typeof value === 'string' ? value : undefined
|
|
}
|
|
|
|
export function getActiveSortColumn(sort: string): string | null {
|
|
return sort ? (sort.startsWith('-') ? sort.slice(1) : sort) : null
|
|
}
|
|
|
|
export function withRowKeys<T>(items: T[]): T[] {
|
|
return items.map((item, index) => {
|
|
const row = item as Record<string, unknown>
|
|
const customer = row.customer
|
|
const customerId = customer && typeof customer === 'object' && 'id' in customer ? (customer as Record<string, unknown>).id : undefined
|
|
const id = row.id ?? customerId
|
|
|
|
return { ...row, id: id != null && id !== '' ? coerceToString(id) : `row-${index}` } as T
|
|
})
|
|
}
|
|
|
|
export function serializeFilters(filters: PaginatedListFilters): Record<string, string> {
|
|
const result: Record<string, string> = {}
|
|
|
|
Object.entries(filters).forEach(([key, value]) => {
|
|
if (value === 'null' || value === '') return
|
|
if (value && typeof value === 'object') {
|
|
const serialized = `${value.from ?? ''},${value.to ?? ''}`
|
|
|
|
if (serialized !== ',') result[key] = serialized
|
|
} else {
|
|
result[key] = coerceToString(value)
|
|
}
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
export function buildListParams({
|
|
filterPrefix,
|
|
filters,
|
|
page,
|
|
resultType,
|
|
rowsPerPage,
|
|
sort,
|
|
urlParams,
|
|
}: {
|
|
filterPrefix?: string
|
|
filters: PaginatedListFilters
|
|
page: number
|
|
resultType?: number
|
|
rowsPerPage: number
|
|
sort: string
|
|
urlParams: ApiWithParams
|
|
}) {
|
|
const cleanUrlParams = removeEmptyFields(urlParams)
|
|
const inheritedFilters =
|
|
cleanUrlParams.filters && typeof cleanUrlParams.filters === 'object' && !Array.isArray(cleanUrlParams.filters)
|
|
? (cleanUrlParams.filters as Record<string, string>)
|
|
: {}
|
|
let params: Record<string, unknown> = {
|
|
...cleanUrlParams,
|
|
page,
|
|
pageSize: rowsPerPage,
|
|
filters: { ...inheritedFilters, ...serializeFilters(filters) },
|
|
sort,
|
|
...(resultType === undefined ? {} : { resultType }),
|
|
}
|
|
|
|
if (filterPrefix) {
|
|
params = Object.fromEntries(Object.entries(params).map(([key, value]) => [`${filterPrefix}.${key}`, value]))
|
|
}
|
|
|
|
return removeEmptyFields(params)
|
|
}
|