Files
khata-ui/react-openapi/api/client.ts
Vishesh 'ironeagle' Bangotra 7bd946ec7a Refactor the React OpenAPI admin framework to support fully customizable field rendering and UI composition. (#11)
# Summary

Refactor the React OpenAPI admin framework to support fully customizable field rendering and UI composition.

## Changes

### Admin UI Customization

* Added support for custom:

  * Dashboard component
  * Layout component
  * Login page component
* Introduced `AdminAppProps` and extended `Admin` configuration API.
* Renamed internal dashboard implementation to `DefaultDashboard`.

### Field Component Architecture

* Extracted field rendering into dedicated field components:

  * TextField
  * NumberField
  * BooleanField
  * DateField
  * EnumField
  * RelationField
  * ObjectField
  * FallbackField
  * DateRangeField
  * NumberRangeField
* Added `defaultFieldComponents` registry.
* Refactored `FormField` to resolve components dynamically from a component map instead of hardcoded field type handling.

### Resource Customization

* Added `FieldComponents` support across:

  * Admin
  * ResourceView
  * GenericForm
  * useResource
* Introduced wrapped `FormField` and `GenericForm` components generated from configured field overrides.

### Table Customization

* Added `EnhancedTableComponents`.
* Added support for custom cell renderers per field type.
* Enabled custom rendering for both desktop and mobile table layouts.

### Filter Improvements

* Exported `FilterAutocomplete`.
* Added support for custom date-range and number-range filter components.
* Added filter component extension points.
* Updated filter option label resolution to support `displayFormat`.

### Display Formatting

* Replaced `displayField` usage with `displayFormat`.
* Added template-based display rendering support through `resolveTemplate`.
* Improved relation display configuration handling.

### TypeScript Improvements

* Added TypeScript as a project dependency.
* Removed multiple `@ts-ignore` usages.
* Added strongly typed Axios wrapper methods with generic response support.
* Improved typing across hooks and component interfaces.

### OpenAPI Configuration Validation

* Added validation for enum fields without enum values.
* Added validation for relation resources missing `referenceOptions.enumOption`.
* Improved relation metadata propagation during schema parsing.

### Library Exports

* Exported:

  * Field component types
  * Override types
  * EnhancedTable
  * GenericForm
  * ResourceView
  * Field components and defaults
* Expanded public API surface for consumers extending the framework.

## Benefits

* Enables complete UI customization without modifying framework internals.
* Simplifies creation of custom field types and renderers.
* Improves type safety and developer experience.
* Provides consistent extension points for forms, tables, filters, and admin layouts.
* Makes the framework more suitable for reusable library distribution.

Reviewed-on: #11
Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
2026-06-07 12:35:52 +00:00

71 lines
2.5 KiB
TypeScript

import axios, { AxiosInstance } from "axios";
import type { AxiosResponse } from "axios";
import { createApiClient } from "../../react-auth";
/**
* We expose a singleton-like getter/setter for the API clients
*/
let _api: AxiosInstance | null = null;
let _auth: AxiosInstance | null = null;
function withParamsSerializer(instance: AxiosInstance): AxiosInstance {
instance.defaults.paramsSerializer = {
serialize: (params) => {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((v) => {
searchParams.append(key, String(v)); // NO []
});
} else if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
});
return searchParams.toString();
},
};
return instance;
}
export const api = {
get: <T = any, R = AxiosResponse<T>>(url: string, config?: Parameters<AxiosInstance["get"]>[1]) => {
if (!_api) throw new Error("API client not initialized");
return _api.get<T, R>(url, config);
},
post: <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: Parameters<AxiosInstance["post"]>[2]) => {
if (!_api) throw new Error("API client not initialized");
return _api.post<T, R>(url, data, config);
},
put: <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: Parameters<AxiosInstance["put"]>[2]) => {
if (!_api) throw new Error("API client not initialized");
return _api.put<T, R>(url, data, config);
},
delete: <T = any, R = AxiosResponse<T>>(url: string, config?: Parameters<AxiosInstance["delete"]>[1]) => {
if (!_api) throw new Error("API client not initialized");
return _api.delete<T, R>(url, config);
},
patch: <T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: Parameters<AxiosInstance["patch"]>[2]) => {
if (!_api) throw new Error("API client not initialized");
return _api.patch<T, R>(url, data, config);
},
};
export const auth = {
post: (...args: Parameters<AxiosInstance["post"]>) => {
if (!_auth) throw new Error("Auth client not initialized");
return _auth.post(...args);
},
get: (...args: Parameters<AxiosInstance["get"]>) => {
if (!_auth) throw new Error("Auth client not initialized");
return _auth.get(...args);
},
};
export function initializeApiClients(baseUrl: string, authBaseUrl: string) {
_api = withParamsSerializer(createApiClient(baseUrl));
_auth = withParamsSerializer(createApiClient(authBaseUrl));
}