From fa44a5b8d893db7446d9b39d021e5585a499cbb0 Mon Sep 17 00:00:00 2001 From: rizky Date: Tue, 16 Apr 2024 00:39:50 -0700 Subject: [PATCH] fix --- comps/form/field/FieldInput.tsx | 4 +- comps/form/field/raw/FieldLoading.tsx | 2 +- comps/form/field/type/TypeRelation.tsx | 79 +++++++++- comps/form/typings.ts | 1 + gen/gen_form/gen_form.ts | 4 +- gen/gen_form/new_field.ts | 12 +- gen/gen_relation/gen_relation.ts | 146 +++++++++++++++++- gen/prop/gen_prop_fields.ts | 195 ++++++++++++++++++++----- gen/utils.ts | 6 +- 9 files changed, 401 insertions(+), 48 deletions(-) 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] = {