This commit is contained in:
2026-07-17 14:31:39 +05:30
parent 83ecdcb500
commit 6c720c390c
6 changed files with 134 additions and 8 deletions

View File

@@ -8,5 +8,7 @@ export { SseStreamView } from "./src/components/SseStreamView";
export { SseConnectionStatus } from "./src/components/SseConnectionStatus"; export { SseConnectionStatus } from "./src/components/SseConnectionStatus";
export { getApi } from "./src/hooks/useApi"; export { getApi } from "./src/hooks/useApi";
export { useItemSse } from "./src/hooks/useItemSse"; export { useItemSse } from "./src/hooks/useItemSse";
export { sanitizePayload } from "./src/utils/sanitize-payload";
export type { FkResolver } from "./src/utils/sanitize-payload";
export type { FilterComponentProps } from "./src/context/useResource"; export type { FilterComponentProps } from "./src/context/useResource";
export type { SpecConfiguration, ResourceConfig, FieldConfig, FKFieldConfig, ResourceRelationship } from "./src/types"; export type { SpecConfiguration, ResourceConfig, FieldConfig, FKFieldConfig, ResourceRelationship } from "./src/types";

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { TextField } from "@mui/material"; import { TextField, Chip, Box } from "@mui/material";
import type { FieldConfig } from "../../types"; import type { FieldConfig } from "../../types";
import { StringField } from "./renderers/StringField"; import { StringField } from "./renderers/StringField";
import { NumberField } from "./renderers/NumberField"; import { NumberField } from "./renderers/NumberField";
@@ -139,6 +139,32 @@ export function FormFieldRenderer({ field, value, onChange, error, fkOptions, fk
); );
} }
if (field.type === "array" && !field.fk) {
return (
<Box>
<Chip
label={`${field.label} (${Array.isArray(value) ? value.length : 0})`}
size="small"
color="default"
variant="outlined"
/>
</Box>
);
}
if (field.type === "object" && !field.fk) {
return (
<Box>
<Chip
label={field.label}
size="small"
color="default"
variant="outlined"
/>
</Box>
);
}
return ( return (
<StringField <StringField
field={field} field={field}

View File

@@ -15,13 +15,21 @@ export function DiscriminatorField({ field, value, onChange, error }: Props) {
const discProp = field.discriminatorProperty ?? "type"; const discProp = field.discriminatorProperty ?? "type";
const currentType = value?.[discProp] ?? ""; const currentType = value?.[discProp] ?? "";
function defaultFieldValue(field: FieldConfig): any {
if (field.enumValues) return field.enumValues[0];
if (field.isArray) return [];
if (field.type === "object") return {};
if (field.type === "number" || field.type === "integer") return 0;
return null;
}
const handleTypeChange = useCallback((e: any) => { const handleTypeChange = useCallback((e: any) => {
const newType = e.target.value; const newType = e.target.value;
const option = options.find((o) => o.value === newType); const option = options.find((o) => o.value === newType);
const newValue: Record<string, any> = { [discProp]: newType }; const newValue: Record<string, any> = { [discProp]: newType };
if (option) { if (option) {
for (const f of option.fields) { for (const f of option.fields) {
newValue[f.name] = f.enumValues?.[0] ?? f.type === "number" ? 0 : f.type === "integer" ? 0 : ""; newValue[f.name] = defaultFieldValue(f);
} }
} }
onChange(newValue); onChange(newValue);

View File

@@ -243,6 +243,10 @@ function buildDefaultShape(fields: FieldConfig[], schemas: Record<string, any>):
const refSchemaObj = schemas[f.refSchema!]; const refSchemaObj = schemas[f.refSchema!];
const nestedFields = refSchemaObj ? extractFields(f.refSchema!, refSchemaObj, schemas) : []; const nestedFields = refSchemaObj ? extractFields(f.refSchema!, refSchemaObj, schemas) : [];
shape[f.name] = f.isArray ? [] : buildDefaultShape(nestedFields, schemas); shape[f.name] = f.isArray ? [] : buildDefaultShape(nestedFields, schemas);
} else if (f.isArray) {
shape[f.name] = [];
} else if (f.type === "object") {
shape[f.name] = {};
} else { } else {
shape[f.name] = null; shape[f.name] = null;
} }
@@ -254,7 +258,7 @@ function initEditValue(value: any, field: FieldConfig, schemas: Record<string, a
if (field.isArray) { if (field.isArray) {
return value ? value.map((item: any) => ({ ...item })) : []; return value ? value.map((item: any) => ({ ...item })) : [];
} }
if (value && typeof value === "object") { if (value != null && value !== "" && typeof value === "object") {
return { ...value }; return { ...value };
} }
return buildDefaultShape( return buildDefaultShape(

View File

@@ -4,6 +4,7 @@ import DoneIcon from "@mui/icons-material/Done";
import type { ResourceConfig, ParsedListResponse, FieldConfig } from "../types"; import type { ResourceConfig, ParsedListResponse, FieldConfig } from "../types";
import { useAppContext } from "./AppContext"; import { useAppContext } from "./AppContext";
import { getApi } from "../hooks/useApi"; import { getApi } from "../hooks/useApi";
import { sanitizePayload } from "../utils/sanitize-payload";
import { StringField } from "../components/fields/renderers/StringField"; import { StringField } from "../components/fields/renderers/StringField";
import { NumberField } from "../components/fields/renderers/NumberField"; import { NumberField } from "../components/fields/renderers/NumberField";
import { DateField } from "../components/fields/renderers/DateField"; import { DateField } from "../components/fields/renderers/DateField";
@@ -588,7 +589,7 @@ function buildFilterComponent(field: FieldConfig, resourceName: string): React.F
} }
export function useResource(resourceName: string): UseResourceReturn { export function useResource(resourceName: string): UseResourceReturn {
const { resources } = useAppContext(); const { resources, schemas } = useAppContext();
const resource = resources.find((r) => r.name === resourceName); const resource = resources.find((r) => r.name === resourceName);
const [state, setState] = useState<ResourceState>({ loading: false, error: null }); const [state, setState] = useState<ResourceState>({ loading: false, error: null });
@@ -661,6 +662,18 @@ export function useResource(resourceName: string): UseResourceReturn {
[rPath, setLoading, setError] [rPath, setLoading, setError]
); );
const resolveFk = useCallback(async (resourceName: string, id: any) => {
const targetRes = resources.find((r) => r.name === resourceName);
if (!targetRes) return id;
try {
const api = getApi();
const res = await api.get(`${targetRes.path}/${id}`);
return res.data;
} catch {
return id;
}
}, [resources]);
const create = useCallback( const create = useCallback(
async (data: any): Promise<any> => { async (data: any): Promise<any> => {
if (!rPath) throw new Error(`Resource "${resourceName}" not found yet`); if (!rPath) throw new Error(`Resource "${resourceName}" not found yet`);
@@ -668,7 +681,10 @@ export function useResource(resourceName: string): UseResourceReturn {
setError(null); setError(null);
try { try {
const api = getApi(); const api = getApi();
const res = await api.post(rPath, data); const sanitized = rFields && schemas
? await sanitizePayload(data, rFields, schemas, resolveFk)
: data;
const res = await api.post(rPath, sanitized);
return res.data; return res.data;
} catch (e: any) { } catch (e: any) {
setError(parseError(e)); setError(parseError(e));
@@ -677,7 +693,7 @@ export function useResource(resourceName: string): UseResourceReturn {
setLoading(false); setLoading(false);
} }
}, },
[rPath, setLoading, setError] [rPath, rFields, schemas, resolveFk, setLoading, setError]
); );
const update = useCallback( const update = useCallback(
@@ -687,8 +703,11 @@ export function useResource(resourceName: string): UseResourceReturn {
setError(null); setError(null);
try { try {
const api = getApi(); const api = getApi();
const sanitized = rFields && schemas
? await sanitizePayload(data, rFields, schemas, resolveFk)
: data;
const method = rUpdateMethod ?? "put"; const method = rUpdateMethod ?? "put";
const res = await (method === "patch" ? api.patch : api.put)(`${rPath}/${id}`, data); const res = await (method === "patch" ? api.patch : api.put)(`${rPath}/${id}`, sanitized);
return res.data; return res.data;
} catch (e: any) { } catch (e: any) {
setError(parseError(e)); setError(parseError(e));
@@ -697,7 +716,7 @@ export function useResource(resourceName: string): UseResourceReturn {
setLoading(false); setLoading(false);
} }
}, },
[rPath, rUpdateMethod, setLoading, setError] [rPath, rFields, schemas, resolveFk, rUpdateMethod, setLoading, setError]
); );
const remove = useCallback( const remove = useCallback(

View File

@@ -0,0 +1,67 @@
import type { FieldConfig } from "../types";
import { extractFields } from "../transformers/field-config";
export type FkResolver = (resourceName: string, fkValue: any) => Promise<any>;
export async function sanitizePayload(
data: any,
fields: FieldConfig[],
schemas: Record<string, any>,
resolveFk?: FkResolver,
): Promise<any> {
if (data == null || typeof data !== "object") return data;
if (Array.isArray(data)) {
return Promise.all(data.map((item) => {
if (item != null && typeof item === "object") {
return sanitizePayload(item, fields, schemas, resolveFk);
}
return item;
}));
}
const result: Record<string, any> = { ...data };
const fieldMap = new Map(fields.map((f) => [f.name, f]));
for (const key of Object.keys(result)) {
const field = fieldMap.get(key);
if (!field) continue;
const val = result[key];
if (field.fk) {
if (val == null || val === "") {
result[key] = null;
} else if (resolveFk) {
if (field.isArray && Array.isArray(val)) {
result[key] = await Promise.all(val.map((v: any) => resolveFk(field.fk!.resource, v)));
} else {
result[key] = await resolveFk(field.fk!.resource, val);
}
}
continue;
}
if (field.refSchema && !field.fk) {
if (val == null || val === "") {
result[key] = field.isArray ? [] : {};
} else if (typeof val === "object") {
const refSchemaObj = schemas[field.refSchema!];
const nestedFields = refSchemaObj
? extractFields(field.refSchema!, refSchemaObj, schemas)
: [];
result[key] = await sanitizePayload(val, nestedFields, schemas, resolveFk);
}
} else if (field.isArray) {
if (val == null || val === "") {
result[key] = [];
}
} else if (field.type === "object") {
if (val == null || val === "") {
result[key] = {};
}
}
}
return result;
}