This commit is contained in:
rizky 2024-04-16 00:39:50 -07:00
parent c98e77afa6
commit fa44a5b8d8
9 changed files with 401 additions and 48 deletions

View File

@ -46,6 +46,7 @@ export const FieldInput: FC<{
const props = found.component.props; const props = found.component.props;
let should_update = false; let should_update = false;
for (const [k, v] of Object.entries(item.component.props) as any) { for (const [k, v] of Object.entries(item.component.props) as any) {
if (props[k] && props[k].valueBuilt === v.valueBuilt) { if (props[k] && props[k].valueBuilt === v.valueBuilt) {
continue; continue;
@ -88,9 +89,8 @@ export const FieldInput: FC<{
: field.focused && : field.focused &&
"c-border-blue-700 c-outline c-outline-blue-700", "c-border-blue-700 c-outline c-outline-blue-700",
css` css`
> div { & > .field-inner {
min-height: 35px; min-height: 35px;
line-height: 35px;
} }
` `
)} )}

View File

@ -2,7 +2,7 @@ import { Skeleton } from "@/comps/ui/skeleton";
export const FieldLoading = () => { export const FieldLoading = () => {
return ( return (
<div className="c-flex c-flex-col c-space-y-1 c-p-1 c-justify-center"> <div className="field-inner c-flex c-flex-col c-space-y-1 c-p-1 c-justify-center">
<div className="c-flex c-space-x-1"> <div className="c-flex c-space-x-1">
<Skeleton <Skeleton
className={css` className={css`

View File

@ -10,6 +10,10 @@ export type PropTypeRelation = {
on_load: (opt: { value?: any }) => Promise<{ items: any[]; pk: string }>; on_load: (opt: { value?: any }) => Promise<{ items: any[]; pk: string }>;
label: (item: any, pk: string) => string; label: (item: any, pk: string) => string;
id_parent: string; id_parent: string;
has_many: "checkbox" | "typeahead";
has_many_list: (opt: {
value?: any;
}) => Promise<{ value: string; label: string }[]>;
}; };
export const FieldTypeRelation: FC<{ export const FieldTypeRelation: FC<{
field: FieldLocal; field: FieldLocal;
@ -17,6 +21,79 @@ export const FieldTypeRelation: FC<{
prop: PropTypeRelation; prop: PropTypeRelation;
PassProp: any; PassProp: any;
child: any; child: any;
}> = (props) => {
if (props.prop.type === "has-one") return <HasOne {...props} />;
return <HasMany {...props} />;
};
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 (
<div className="c-flex c-flex-col c-p-2 c-items-stretch">
{input.many.map((e, idx) => {
return (
<label key={idx} className="c-flex c-items-center c-space-x-1">
<input type="checkbox" value={e.value} />
<div className="c-flex-1">{e.label}</div>
</label>
);
})}
</div>
);
};
const HasOne: FC<{
field: FieldLocal;
fm: FMLocal;
prop: PropTypeRelation;
PassProp: any;
child: any;
}> = ({ field, fm, prop, PassProp, child }) => { }> = ({ field, fm, prop, PassProp, child }) => {
const input = useLocal({ const input = useLocal({
list: null as null | any[], list: null as null | any[],
@ -27,7 +104,7 @@ export const FieldTypeRelation: FC<{
field.prop = prop; field.prop = prop;
useEffect(() => { useEffect(() => {
if (input.list === null) { if (!isEditor && input.list === null) {
field.status = "loading"; field.status = "loading";
field.render(); field.render();

View File

@ -22,6 +22,7 @@ export type FMProps = {
label_mode: "vertical" | "horizontal" | "hidden"; label_mode: "vertical" | "horizontal" | "hidden";
label_width: number; label_width: number;
gen_fields: any; gen_fields: any;
gen_table: string;
}; };
export type FieldProp = { export type FieldProp = {

View File

@ -61,7 +61,9 @@ export const gen_form = async (modify: (data: any) => void, data: any) => {
result["body"] = data["body"]; result["body"] = data["body"];
result.body.content.childs = []; result.body.content.childs = [];
for (const item of fields.filter((e) => !e.is_pk)) { 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); modify(result);

View File

@ -1,6 +1,7 @@
import { createId } from "@paralleldrive/cuid2"; import { createId } from "@paralleldrive/cuid2";
import { GFCol, createItem, formatName } from "../utils"; import { GFCol, createItem, formatName } from "../utils";
import { gen_relation } from "../gen_relation/gen_relation"; import { gen_relation } from "../gen_relation/gen_relation";
import { FMLocal } from "@/comps/form/typings";
export const newItem = (component: { export const newItem = (component: {
id: string; id: string;
props: Record<string, string>; props: Record<string, string>;
@ -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 = []; const childs = [];
let type = "text"; let type = "text";
@ -62,11 +63,18 @@ export const newField = async (arg: GFCol) => {
label: [`() => {}`], label: [`() => {}`],
gen_table: arg.relation.to.table, gen_table: arg.relation.to.table,
gen_fields: [value, value], 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: {}, 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); childs.push(item);
} }

View File

@ -1,4 +1,5 @@
import { codeBuild } from "../master_detail/utils"; import { codeBuild } from "../master_detail/utils";
import { gen_prop_fields } from "../prop/gen_prop_fields";
import { GFCol, parseGenField } from "../utils"; import { GFCol, parseGenField } from "../utils";
import { newField } from "./new_field"; import { newField } from "./new_field";
import { on_load } from "./on_load"; import { on_load } from "./on_load";
@ -6,19 +7,42 @@ import { on_load } from "./on_load";
export const gen_relation = async ( export const gen_relation = async (
modify: (data: any) => void, modify: (data: any) => void,
data: any, 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 table = JSON.parse(data.gen_table.value);
const raw_fields = JSON.parse(data.gen_fields.value) as ( const raw_fields = JSON.parse(data.gen_fields.value) as (
| string | string
| { value: string; checked: string[] } | { value: string; checked: string[] }
)[]; )[];
const select = {} as any;
let pk: null | GFCol = null;
let pks: Record<string, string> = {};
const fields = parseGenField(raw_fields); 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<string, string> = {};
const select = {} as any;
const result = {} as any; const result = {} as any;
for (const f of fields) { for (const f of fields) {
select[f.name] = true; select[f.name] = true;
if (f.relation) { 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<string, string> = {};
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; select[arg.id_parent] = true;
} }

View File

@ -1,4 +1,43 @@
const cache = {} as Record<string, any>; const cache = {} as Record<string, any>;
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) => { export const gen_prop_fields = async (gen_table: string) => {
if (cache[gen_table]) return cache[gen_table]; if (cache[gen_table]) return cache[gen_table];
@ -9,8 +48,9 @@ export const gen_prop_fields = async (gen_table: string) => {
options?: any[]; options?: any[];
checked?: boolean; checked?: boolean;
}[] = []; }[] = [];
const fields = await db._schema.columns(gen_table); const { cols, rels } = await load_single(gen_table);
for (const [k, v] of Object.entries(fields)) { if (cols) {
for (const [k, v] of Object.entries(cols) as any) {
result.push({ result.push({
value: JSON.stringify({ value: JSON.stringify({
name: k, name: k,
@ -22,13 +62,15 @@ export const gen_prop_fields = async (gen_table: string) => {
checked: v.is_pk, checked: v.is_pk,
}); });
} }
const rels = await db._schema.rels(gen_table); }
if (rels) {
for (const [k, v] of Object.entries(rels)) { for (const [k, v] of Object.entries(rels)) {
let options = []; let options = [];
const to = v.to; const to = v.to;
const from = v.from; const from = v.from;
const fields = await db._schema.columns(v.to.table); const { cols, rels } = await load_single(v.to.table);
for (const [k, v] of Object.entries(fields)) { if (cols) {
for (const [k, v] of Object.entries(cols)) {
options.push({ options.push({
value: JSON.stringify({ value: JSON.stringify({
name: k, name: k,
@ -40,6 +82,38 @@ export const gen_prop_fields = async (gen_table: string) => {
checked: v.is_pk, 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({ result.push({
value: JSON.stringify({ value: JSON.stringify({
name: k, name: k,
@ -52,10 +126,61 @@ export const gen_prop_fields = async (gen_table: string) => {
options, options,
}); });
} }
if (!cache[gen_table]) {
cache[gen_table] = result;
} }
return result; 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;
// }

View File

@ -60,7 +60,10 @@ type SimplifiedItem = {
name?: string; name?: string;
component?: { component?: {
id: string; id: string;
props: Record<string, string | SimplifiedItem | [any] | [any, any]>; props: Record<
string,
string | SimplifiedItem | [any] | [any, any] | undefined
>;
}; };
childs?: SimplifiedItem[]; childs?: SimplifiedItem[];
adv?: { adv?: {
@ -80,6 +83,7 @@ export const createItem = (arg: SimplifiedItem): any => {
if (arg.component.props) { if (arg.component.props) {
for (const [k, v] of Object.entries(arg.component.props)) { for (const [k, v] of Object.entries(arg.component.props)) {
if (v === undefined) continue;
if (typeof v === "object") { if (typeof v === "object") {
if (Array.isArray(v) && v.length === 1) { if (Array.isArray(v) && v.length === 1) {
component.props[k] = { component.props[k] = {