diff --git a/comps/form/field/FieldInput.tsx b/comps/form/field/FieldInput.tsx
index ebefd78..90658a3 100755
--- a/comps/form/field/FieldInput.tsx
+++ b/comps/form/field/FieldInput.tsx
@@ -46,6 +46,7 @@ export const FieldInput: FC<{
const props = found.component.props;
let should_update = false;
+
for (const [k, v] of Object.entries(item.component.props) as any) {
if (props[k] && props[k].valueBuilt === v.valueBuilt) {
continue;
@@ -88,9 +89,8 @@ export const FieldInput: FC<{
: field.focused &&
"c-border-blue-700 c-outline c-outline-blue-700",
css`
- > div {
+ & > .field-inner {
min-height: 35px;
- line-height: 35px;
}
`
)}
diff --git a/comps/form/field/raw/FieldLoading.tsx b/comps/form/field/raw/FieldLoading.tsx
index c10c5f2..e842e98 100755
--- a/comps/form/field/raw/FieldLoading.tsx
+++ b/comps/form/field/raw/FieldLoading.tsx
@@ -2,7 +2,7 @@ import { Skeleton } from "@/comps/ui/skeleton";
export const FieldLoading = () => {
return (
-
+
Promise<{ items: any[]; pk: string }>;
label: (item: any, pk: string) => string;
id_parent: string;
+ has_many: "checkbox" | "typeahead";
+ has_many_list: (opt: {
+ value?: any;
+ }) => Promise<{ value: string; label: string }[]>;
};
export const FieldTypeRelation: FC<{
field: FieldLocal;
@@ -17,6 +21,79 @@ export const FieldTypeRelation: FC<{
prop: PropTypeRelation;
PassProp: any;
child: any;
+}> = (props) => {
+ if (props.prop.type === "has-one") return ;
+ return ;
+};
+
+const HasMany: FC<{
+ field: FieldLocal;
+ fm: FMLocal;
+ prop: PropTypeRelation;
+ PassProp: any;
+ child: any;
+}> = ({ field, fm, prop, PassProp, child }) => {
+ const input = useLocal({
+ list: null as null | any[],
+ many: [] as { value: string; label: string }[],
+ pk: "",
+ });
+ const value = fm.data[field.name];
+ field.input = input;
+ field.prop = prop;
+
+ useEffect(() => {
+ if (!isEditor && input.list === null) {
+ field.status = "loading";
+ field.render();
+
+ const callback = (arg: { items: any[]; pk: string }) => {
+ input.list = arg.items;
+ input.pk = arg.pk;
+ field.status = "ready";
+ input.render();
+ };
+ const res = prop.on_load({ value });
+ if (res instanceof Promise) res.then(callback);
+ else callback(res);
+
+ const many_list_loaded = (arg: { value: string; label: string }[]) => {
+ input.many = arg;
+ input.render();
+ };
+ const many_res = prop.has_many_list({ value });
+ if (res instanceof Promise) many_res.then(many_list_loaded);
+ else many_list_loaded(res);
+ }
+ }, []);
+
+ if (isEditor) {
+ input.many = [
+ { value: "sample 1", label: "sample 1" },
+ { value: "sample 2", label: "sample 2" },
+ ];
+ }
+
+ return (
+
+ {input.many.map((e, idx) => {
+ return (
+
+ );
+ })}
+
+ );
+};
+
+const HasOne: FC<{
+ field: FieldLocal;
+ fm: FMLocal;
+ prop: PropTypeRelation;
+ PassProp: any;
+ child: any;
}> = ({ field, fm, prop, PassProp, child }) => {
const input = useLocal({
list: null as null | any[],
@@ -27,7 +104,7 @@ export const FieldTypeRelation: FC<{
field.prop = prop;
useEffect(() => {
- if (input.list === null) {
+ if (!isEditor && input.list === null) {
field.status = "loading";
field.render();
diff --git a/comps/form/typings.ts b/comps/form/typings.ts
index e9bd024..8f9427f 100755
--- a/comps/form/typings.ts
+++ b/comps/form/typings.ts
@@ -22,6 +22,7 @@ export type FMProps = {
label_mode: "vertical" | "horizontal" | "hidden";
label_width: number;
gen_fields: any;
+ gen_table: string;
};
export type FieldProp = {
diff --git a/gen/gen_form/gen_form.ts b/gen/gen_form/gen_form.ts
index 501e231..160ce32 100755
--- a/gen/gen_form/gen_form.ts
+++ b/gen/gen_form/gen_form.ts
@@ -61,7 +61,9 @@ export const gen_form = async (modify: (data: any) => void, data: any) => {
result["body"] = data["body"];
result.body.content.childs = [];
for (const item of fields.filter((e) => !e.is_pk)) {
- result.body.content.childs.push(await newField(item));
+ result.body.content.childs.push(
+ await newField(item, { parent_table: table })
+ );
}
}
modify(result);
diff --git a/gen/gen_form/new_field.ts b/gen/gen_form/new_field.ts
index 9aeb80a..1897732 100755
--- a/gen/gen_form/new_field.ts
+++ b/gen/gen_form/new_field.ts
@@ -1,6 +1,7 @@
import { createId } from "@paralleldrive/cuid2";
import { GFCol, createItem, formatName } from "../utils";
import { gen_relation } from "../gen_relation/gen_relation";
+import { FMLocal } from "@/comps/form/typings";
export const newItem = (component: {
id: string;
props: Record;
@@ -29,7 +30,7 @@ export type NewFieldArg = {
};
};
-export const newField = async (arg: GFCol) => {
+export const newField = async (arg: GFCol, opt: { parent_table: string }) => {
const childs = [];
let type = "text";
@@ -62,11 +63,18 @@ export const newField = async (arg: GFCol) => {
label: [`() => {}`],
gen_table: arg.relation.to.table,
gen_fields: [value, value],
+ has_many_from:
+ arg.type === "has-many" ? arg.relation.from.table : undefined,
+ has_many_list: arg.type === "has-many" ? [`null`] : undefined,
child: {},
},
},
});
- await gen_relation(() => {}, item.component.props);
+ await gen_relation(() => {}, item.component.props, {
+ id_parent: "",
+ type: arg.type as any,
+ parent_table: opt.parent_table,
+ });
childs.push(item);
}
diff --git a/gen/gen_relation/gen_relation.ts b/gen/gen_relation/gen_relation.ts
index 55e73cb..c2be375 100755
--- a/gen/gen_relation/gen_relation.ts
+++ b/gen/gen_relation/gen_relation.ts
@@ -1,4 +1,5 @@
import { codeBuild } from "../master_detail/utils";
+import { gen_prop_fields } from "../prop/gen_prop_fields";
import { GFCol, parseGenField } from "../utils";
import { newField } from "./new_field";
import { on_load } from "./on_load";
@@ -6,19 +7,42 @@ import { on_load } from "./on_load";
export const gen_relation = async (
modify: (data: any) => void,
data: any,
- arg: { id_parent: string }
+ arg: { id_parent: string; type: "has-many" | "has-one"; parent_table: string }
) => {
const table = JSON.parse(data.gen_table.value);
const raw_fields = JSON.parse(data.gen_fields.value) as (
| string
| { value: string; checked: string[] }
)[];
- const select = {} as any;
- let pk: null | GFCol = null;
- let pks: Record = {};
const fields = parseGenField(raw_fields);
+ if (arg.type === "has-one") {
+ await genHasOne(modify, data, arg, { table, fields });
+ } else {
+ await genHasMany(modify, data, arg, { table, fields });
+ }
+};
+
+const genHasMany = async (
+ modify: (data: any) => void,
+ data: any,
+ arg: {
+ id_parent: string;
+ type: "has-many" | "has-one";
+ parent_table: string;
+ },
+ pass: {
+ table: string;
+ fields: GFCol[];
+ }
+) => {
+ const { table, fields } = pass;
+
+ let pk: null | GFCol = null;
+ let pks: Record = {};
+ const select = {} as any;
const result = {} as any;
+
for (const f of fields) {
select[f.name] = true;
if (f.relation) {
@@ -35,7 +59,119 @@ export const gen_relation = async (
}
}
- if (arg.id_parent) {
+ if (arg && arg.id_parent) {
+ select[arg.id_parent] = true;
+ }
+
+ if (!pk) {
+ alert("Failed to generate! Primary Key not found. ");
+ return;
+ }
+
+ if (pk) {
+ const code = {} as any;
+ if (data["on_load"]) {
+ result["on_load"] = data["on_load"];
+ result["on_load"].value = on_load({
+ pk,
+ pks,
+ select,
+ table,
+ id_parent: arg.id_parent,
+ });
+ code.on_load = result["on_load"].value;
+ }
+
+ if (data["has_many_from"] && arg.parent_table) {
+ result["has_many_from"] = data["has_many_from"];
+ result["has_many_from"].value = `"${arg.parent_table}"`;
+ result["has_many_from"].valueBuilt = `"${arg.parent_table}"`;
+ }
+
+ if (data["has_many_list"] && arg.parent_table) {
+ const defs = parseGenField(
+ (await gen_prop_fields(arg.parent_table)).map((e: any) => e.value)
+ );
+ const pk = defs.find((e) => e.is_pk);
+
+ result["has_many_list"] = data["has_many_list"];
+ result["has_many_list"].value = `\
+async () => {
+ const result: { value: string; label: string }[] = [];
+ const list = await db.${arg.parent_table}.findMany({
+ select: {
+ ${pk}: true,
+ },
+ where: { },
+ });
+ return result;
+}`;
+ code.has_many_list = result["has_many_list"].value;
+ }
+
+ if (data["label"]) {
+ result["label"] = data["label"];
+ result["label"].value = `\
+(item:any, pk:string) => {
+ return \`${Object.entries(select)
+ .filter(([k, v]) => {
+ if (typeof v !== "object" && k !== pk?.name && k !== arg.id_parent) {
+ return true;
+ }
+ })
+ .map(([name]) => {
+ return `\${item.${name}}`;
+ })
+ .join(" ")}\`
+}`;
+ code.on_load = result["on_load"].value;
+ }
+
+ const res = await codeBuild(code);
+ for (const [k, v] of Object.entries(res)) {
+ result[k].valueBuilt = v[1];
+ }
+
+ result["child"] = data["child"];
+ result.child.content.childs = fields.filter((e) => !e.is_pk).map(newField);
+ }
+
+ modify(result);
+};
+
+const genHasOne = async (
+ modify: (data: any) => void,
+ data: any,
+ arg: { id_parent: string; type: "has-many" | "has-one" },
+ pass: {
+ table: string;
+ fields: GFCol[];
+ }
+) => {
+ const { table, fields } = pass;
+
+ let pk: null | GFCol = null;
+ let pks: Record = {};
+ const select = {} as any;
+ const result = {} as any;
+
+ for (const f of fields) {
+ select[f.name] = true;
+ if (f.relation) {
+ select[f.name] = {
+ select: {},
+ };
+ for (const r of f.relation.fields) {
+ select[f.name].select[r.name] = true;
+ }
+ }
+
+ if (f.is_pk) {
+ pk = f;
+ }
+ }
+
+ if (arg && arg.id_parent) {
select[arg.id_parent] = true;
}
diff --git a/gen/prop/gen_prop_fields.ts b/gen/prop/gen_prop_fields.ts
index bf34a14..310fb6f 100755
--- a/gen/prop/gen_prop_fields.ts
+++ b/gen/prop/gen_prop_fields.ts
@@ -1,4 +1,43 @@
const cache = {} as Record;
+const single = {} as Record<
+ string,
+ {
+ cols: Record<
+ string,
+ {
+ is_pk: boolean;
+ type: string;
+ optional: boolean;
+ db_type: string;
+ default?: any;
+ }
+ >;
+ rels: Record<
+ string,
+ {
+ type: "has-many" | "has-one";
+ to: {
+ table: string;
+ fields: string[];
+ };
+ from: {
+ table: string;
+ fields: string[];
+ };
+ }
+ >;
+ }
+>;
+
+const load_single = async (table: string) => {
+ if (!single[table]) {
+ single[table] = {
+ cols: await db._schema.columns(table),
+ rels: await db._schema.rels(table),
+ };
+ }
+ return single[table];
+};
export const gen_prop_fields = async (gen_table: string) => {
if (cache[gen_table]) return cache[gen_table];
@@ -9,27 +48,10 @@ export const gen_prop_fields = async (gen_table: string) => {
options?: any[];
checked?: boolean;
}[] = [];
- const fields = await db._schema.columns(gen_table);
- for (const [k, v] of Object.entries(fields)) {
- result.push({
- value: JSON.stringify({
- name: k,
- is_pk: v.is_pk,
- type: v.db_type || v.type,
- optional: v.optional,
- }),
- label: k,
- checked: v.is_pk,
- });
- }
- const rels = await db._schema.rels(gen_table);
- for (const [k, v] of Object.entries(rels)) {
- let options = [];
- const to = v.to;
- const from = v.from;
- const fields = await db._schema.columns(v.to.table);
- for (const [k, v] of Object.entries(fields)) {
- options.push({
+ const { cols, rels } = await load_single(gen_table);
+ if (cols) {
+ for (const [k, v] of Object.entries(cols) as any) {
+ result.push({
value: JSON.stringify({
name: k,
is_pk: v.is_pk,
@@ -40,22 +62,125 @@ export const gen_prop_fields = async (gen_table: string) => {
checked: v.is_pk,
});
}
- result.push({
- value: JSON.stringify({
- name: k,
- is_pk: false,
- type: v.type,
- optional: true,
- relation: { from, to },
- }),
- label: k,
- options,
- });
}
-
- if (!cache[gen_table]) {
- cache[gen_table] = result;
+ if (rels) {
+ for (const [k, v] of Object.entries(rels)) {
+ let options = [];
+ const to = v.to;
+ const from = v.from;
+ const { cols, rels } = await load_single(v.to.table);
+ if (cols) {
+ for (const [k, v] of Object.entries(cols)) {
+ options.push({
+ value: JSON.stringify({
+ name: k,
+ is_pk: v.is_pk,
+ type: v.db_type || v.type,
+ optional: v.optional,
+ }),
+ label: k,
+ checked: v.is_pk,
+ });
+ }
+ }
+ if (rels) {
+ for (const [k, v] of Object.entries(rels)) {
+ let sub_opt = [];
+ const to = v.to;
+ const from = v.from;
+ const { cols } = await load_single(v.to.table);
+ for (const [k, v] of Object.entries(cols)) {
+ sub_opt.push({
+ value: JSON.stringify({
+ name: k,
+ is_pk: v.is_pk,
+ type: v.db_type || v.type,
+ optional: v.optional,
+ }),
+ label: k,
+ checked: v.is_pk,
+ });
+ }
+ options.push({
+ value: JSON.stringify({
+ name: k,
+ is_pk: false,
+ type: v.type,
+ optional: true,
+ relation: { from, to },
+ }),
+ label: k,
+ options: sub_opt,
+ });
+ }
+ }
+ result.push({
+ value: JSON.stringify({
+ name: k,
+ is_pk: false,
+ type: v.type,
+ optional: true,
+ relation: { from, to },
+ }),
+ label: k,
+ options,
+ });
+ }
}
return result;
};
+
+// const result: {
+// label: string;
+// value: string;
+// options?: any[];
+// checked?: boolean;
+// }[] = [];
+// const fields = await db._schema.columns(gen_table);
+// for (const [k, v] of Object.entries(fields)) {
+// result.push({
+// value: JSON.stringify({
+// name: k,
+// is_pk: v.is_pk,
+// type: v.db_type || v.type,
+// optional: v.optional,
+// }),
+// label: k,
+// checked: v.is_pk,
+// });
+// }
+// const rels = await db._schema.rels(gen_table);
+// for (const [k, v] of Object.entries(rels)) {
+// let options = [];
+// const to = v.to;
+// const from = v.from;
+// const fields = await db._schema.columns(v.to.table);
+// for (const [k, v] of Object.entries(fields)) {
+// options.push({
+// value: JSON.stringify({
+// name: k,
+// is_pk: v.is_pk,
+// type: v.db_type || v.type,
+// optional: v.optional,
+// }),
+// label: k,
+// checked: v.is_pk,
+// });
+// }
+// result.push({
+// value: JSON.stringify({
+// name: k,
+// is_pk: false,
+// type: v.type,
+// optional: true,
+// relation: { from, to },
+// }),
+// label: k,
+// options,
+// });
+// }
+
+// if (!cache[gen_table]) {
+// cache[gen_table] = result;
+// }
diff --git a/gen/utils.ts b/gen/utils.ts
index ae5343d..9444251 100755
--- a/gen/utils.ts
+++ b/gen/utils.ts
@@ -60,7 +60,10 @@ type SimplifiedItem = {
name?: string;
component?: {
id: string;
- props: Record;
+ props: Record<
+ string,
+ string | SimplifiedItem | [any] | [any, any] | undefined
+ >;
};
childs?: SimplifiedItem[];
adv?: {
@@ -80,6 +83,7 @@ export const createItem = (arg: SimplifiedItem): any => {
if (arg.component.props) {
for (const [k, v] of Object.entries(arg.component.props)) {
+ if (v === undefined) continue;
if (typeof v === "object") {
if (Array.isArray(v) && v.length === 1) {
component.props[k] = {