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 | undefined export type DashboardCrudFormOption = { value: string label: string } export type DashboardCrudFormField = { name: string label: string type?: "text" | "textarea" | "number" | "select" placeholder?: string defaultValue?: DashboardCrudFormValue required?: boolean requiredMessage?: string trim?: boolean valueType?: "string" | "number" min?: number max?: number step?: number pattern?: RegExp patternMessage?: string options?: ReadonlyArray loadOptions?: () => Promise> colSpan?: 1 | 2 rows?: number valueFromItem?: (item: TItem) => DashboardCrudFormValue } 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 ?? "" 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, value === undefined || value === null ? "" : String(value)] }) ) } export function normalizeDashboardCrudSubmitValues( fields: ReadonlyArray>, values: Record ): Record { const output: Record = {} fields.forEach((field) => { const rawValue = values[field.name] ?? "" const text = field.trim ? rawValue.trim() : rawValue 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 } 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 }