admin/components/paginated-list/paginatedListUtils.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

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)
}