mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 08:06:00 +00:00
feat: enhance entity field management with configurable order
Added optional `fields_order` to the entity configuration schema, allowing for custom field sorting. Updated `getFields` method to support sorting based on this configuration. Adjusted related methods and UI components to handle field order during updates and serialization. Improved handling of virtual fields in various contexts.
This commit is contained in:
@@ -18,6 +18,7 @@ export const entityConfigSchema = s
|
||||
sort_field: s.string({ default: config.data.default_primary_field }),
|
||||
sort_dir: s.string({ enum: ["asc", "desc"], default: "asc" }),
|
||||
primary_format: s.string({ enum: primaryFieldTypes }),
|
||||
fields_order: s.array(s.string()).optional(),
|
||||
},
|
||||
{ default: {} },
|
||||
)
|
||||
@@ -137,7 +138,9 @@ export class Entity<
|
||||
}
|
||||
|
||||
getFillableFields(context?: TActionContext, include_virtual?: boolean): Field[] {
|
||||
return this.getFields(include_virtual).filter((field) => field.isFillable(context));
|
||||
return this.getFields({ virtual: include_virtual }).filter((field) =>
|
||||
field.isFillable(context),
|
||||
);
|
||||
}
|
||||
|
||||
getRequiredFields(): Field[] {
|
||||
@@ -189,9 +192,24 @@ export class Entity<
|
||||
return this.fields.findIndex((field) => field.name === name) !== -1;
|
||||
}
|
||||
|
||||
getFields(include_virtual: boolean = false): Field[] {
|
||||
if (include_virtual) return this.fields;
|
||||
return this.fields.filter((f) => !f.isVirtual());
|
||||
getFields(opts?: { virtual?: boolean; sorted?: boolean }): Field[] {
|
||||
const fields = [...this.fields];
|
||||
const sort = opts?.sorted !== false;
|
||||
const fields_order = this.config.fields_order;
|
||||
if (fields_order && fields_order.length > 0 && sort) {
|
||||
if (fields_order.length !== this.fields.length) {
|
||||
$console.warn("Fields order must contain all fields");
|
||||
} else {
|
||||
// sort fields by order
|
||||
fields.sort((a, b) => {
|
||||
const a_index = fields_order.indexOf(a.name);
|
||||
const b_index = fields_order.indexOf(b.name);
|
||||
return a_index - b_index;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (opts?.virtual) return fields;
|
||||
return fields.filter((f) => !f.isVirtual());
|
||||
}
|
||||
|
||||
addField(field: Field) {
|
||||
@@ -275,7 +293,7 @@ export class Entity<
|
||||
fields = this.getFillableFields(options.context);
|
||||
break;
|
||||
default:
|
||||
fields = this.getFields(true);
|
||||
fields = this.getFields({ virtual: true });
|
||||
}
|
||||
|
||||
const _fields = Object.fromEntries(fields.map((field) => [field.name, field]));
|
||||
@@ -309,7 +327,12 @@ export class Entity<
|
||||
toJSON() {
|
||||
return {
|
||||
type: this.type,
|
||||
fields: Object.fromEntries(this.fields.map((field) => [field.name, field.toJSON()])),
|
||||
fields: Object.fromEntries(
|
||||
this.getFields({ virtual: true, sorted: true }).map((field) => [
|
||||
field.name,
|
||||
field.toJSON(),
|
||||
]),
|
||||
),
|
||||
config: this.config,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export class SchemaManager {
|
||||
}
|
||||
|
||||
getIntrospectionFromEntity(entity: Entity): IntrospectedTable {
|
||||
const fields = entity.getFields(false);
|
||||
const fields = entity.getFields({ virtual: false, sorted: true });
|
||||
const indices = this.em.getIndicesOf(entity);
|
||||
|
||||
// this is intentionally setting values to defaults, like "nullable" and "default"
|
||||
|
||||
@@ -120,17 +120,20 @@ function entityFieldActions(bkndActions: TSchemaActions, entityName: string) {
|
||||
return await bkndActions.add("data", `entities.${entityName}.fields.${name}`, validated);
|
||||
},
|
||||
patch: () => null,
|
||||
set: async (fields: TAppDataEntityFields) => {
|
||||
set: async (fields: TAppDataEntityFields, order?: string[]) => {
|
||||
try {
|
||||
const validated = parse(entityFields, fields, {
|
||||
skipMark: true,
|
||||
forceParse: true,
|
||||
});
|
||||
const res = await bkndActions.overwrite(
|
||||
await bkndActions.overwrite("data", `entities.${entityName}.fields`, validated);
|
||||
if (order) {
|
||||
await bkndActions.overwrite(
|
||||
"data",
|
||||
`entities.${entityName}.fields`,
|
||||
validated,
|
||||
`entities.${entityName}.config.fields_order`,
|
||||
order,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("error", e);
|
||||
if (e instanceof InvalidSchemaError) {
|
||||
|
||||
@@ -155,18 +155,25 @@ const Fields = ({ entity }: { entity: Entity }) => {
|
||||
const { readonly } = useBknd();
|
||||
const [res, setRes] = useState<any>();
|
||||
const ref = useRef<EntityFieldsFormRef>(null);
|
||||
|
||||
// @todo: the return of toJSON from Fields doesn't match "type" enum
|
||||
const initialFields = Object.fromEntries(entity.fields.map((f) => [f.name, f.toJSON()])) as any;
|
||||
|
||||
async function handleUpdate() {
|
||||
if (submitting) return;
|
||||
setSubmitting(true);
|
||||
const fields = ref.current?.getData()!;
|
||||
|
||||
await actions.entity.patch(entity.name).fields.set(fields);
|
||||
|
||||
// check if field order has changed
|
||||
if (Object.keys(initialFields).join(",") !== Object.keys(fields).join(",")) {
|
||||
await actions.entity.patch(entity.name).fields.set(fields, Object.keys(fields));
|
||||
}
|
||||
setSubmitting(false);
|
||||
setUpdates((u) => u + 1);
|
||||
}
|
||||
|
||||
// @todo: the return of toJSON from Fields doesn't match "type" enum
|
||||
const initialFields = Object.fromEntries(entity.fields.map((f) => [f.name, f.toJSON()])) as any;
|
||||
|
||||
return (
|
||||
<AppShell.RouteAwareSectionHeaderAccordionItem
|
||||
identifier="fields"
|
||||
|
||||
Reference in New Issue
Block a user