import type { ReactNode } from "react" export type DashboardCrudQueryValue = string | number | undefined export type DashboardCrudQueryFilter = { name: string trim?: boolean allValue?: string | number valueType?: "string" | "number" } export type DashboardCrudFilterStateConfig< TValue extends string | number = string | number, > = { name: string defaultValue: TValue } export type DashboardCrudPageResult = { results: T[] page: { page: number limit: number total: number } } export type DashboardCrudFormValue = | string | number | boolean | ReadonlyArray | undefined export type DashboardCrudFormOption = { value: string label: string } export type DashboardCrudFormInputValue = string | boolean | string[] export type DashboardCrudFormCustomRenderContext = { name: string label: string value: DashboardCrudFormInputValue values: Record setValue: (name: string, value: DashboardCrudFormInputValue) => void } export type DashboardCrudFormField = { name: string label: string type?: | "text" | "textarea" | "number" | "select" | "multiSelect" | "switch" | "checkbox" | "password" | "json" | "code" | "custom" | "section" | "group" placeholder?: string defaultValue?: DashboardCrudFormValue description?: string required?: boolean requiredMessage?: string trim?: boolean valueType?: "string" | "number" | "boolean" min?: number max?: number step?: number pattern?: RegExp patternMessage?: string options?: ReadonlyArray loadOptions?: () => Promise> colSpan?: 1 | 2 rows?: number language?: string validateJson?: boolean valueFromItem?: (item: TItem) => DashboardCrudFormValue render?: (context: DashboardCrudFormCustomRenderContext) => ReactNode } export type DashboardCrudActionRule = { visible?: (item: TItem) => boolean disabled?: (item: TItem) => boolean } export function buildDashboardCrudQuery({ values, filters, page, limit, }: { values: Record filters: DashboardCrudQueryFilter[] page: number limit: number }): Record { const query: Record = {} filters.forEach((filter) => { const rawValue = values[filter.name] const value = filter.trim && typeof rawValue === "string" ? rawValue.trim() : rawValue if ( value === undefined || value === "" || (filter.allValue !== undefined && String(value) === String(filter.allValue)) ) { return } if (filter.valueType === "number") { const numberValue = Number(value) if (Number.isFinite(numberValue)) { query[filter.name] = numberValue } return } query[filter.name] = value }) query.page = page query.limit = limit return query } export function buildDashboardCrudInitialFilters( filters: ReadonlyArray ): Record { return Object.fromEntries( filters.map((filter) => [filter.name, filter.defaultValue]) ) as Record } export function normalizeDashboardCrudPageResult( result: Partial> | null | undefined, page: number, limit: number ): DashboardCrudPageResult { return { results: Array.isArray(result?.results) ? result.results : [], page: { page: result?.page?.page ?? page, limit: result?.page?.limit ?? limit, total: result?.page?.total ?? 0, }, } } export function buildDashboardCrudFormValues( fields: ReadonlyArray>, item?: TItem | null ): Record { return Object.fromEntries( fields.map((field) => { let value: unknown = field.defaultValue ?? getDashboardCrudFormDefaultValue(field) if (item) { if (field.valueFromItem) { value = field.valueFromItem(item) } else if (typeof item === "object" && item && field.name in item) { value = (item as Record)[field.name] } } return [field.name, normalizeDashboardCrudFormInputValue(field, value)] }) ) } export function normalizeDashboardCrudSubmitValues( fields: ReadonlyArray>, values: Record ): Record { const output: Record = {} fields.forEach((field) => { if (field.type === "section" || field.type === "group") { return } const rawValue = values[field.name] ?? getDashboardCrudFormDefaultValue(field) if (field.type === "switch" || field.type === "checkbox" || field.valueType === "boolean") { output[field.name] = Boolean(rawValue) return } if (field.type === "multiSelect") { const list = Array.isArray(rawValue) ? rawValue : [] if (field.valueType === "number") { output[field.name] = list .map((value) => Number(value)) .filter((value) => Number.isFinite(value)) return } output[field.name] = list.map(String) return } const rawText = typeof rawValue === "string" ? rawValue : String(rawValue ?? "") const text = field.trim ? rawText.trim() : rawText if (field.type === "number" || field.valueType === "number") { const numberValue = Number(text) output[field.name] = Number.isFinite(numberValue) ? numberValue : 0 return } output[field.name] = text }) return output } function getDashboardCrudFormDefaultValue( field: DashboardCrudFormField ): DashboardCrudFormInputValue { if (field.type === "switch" || field.type === "checkbox") { return false } if (field.type === "multiSelect") { return [] } return "" } function normalizeDashboardCrudFormInputValue( field: DashboardCrudFormField, value: unknown ): DashboardCrudFormInputValue { if (value === undefined || value === null) { return getDashboardCrudFormDefaultValue(field) } if (field.type === "switch" || field.type === "checkbox" || field.valueType === "boolean") { return value === true || value === "true" || value === 1 || value === "1" } if (field.type === "multiSelect") { if (!Array.isArray(value)) return [] return value .filter((item) => item !== undefined && item !== null) .map((item) => String(item)) } return String(value) } export function isDashboardCrudActionVisible( action: DashboardCrudActionRule, item: TItem ) { return action.visible ? action.visible(item) : true } export function isDashboardCrudActionDisabled( action: DashboardCrudActionRule, item: TItem ) { return action.disabled ? action.disabled(item) : false }