diff --git a/comps/custom/Breadcrumb.tsx b/comps/custom/Breadcrumb.tsx index bdef0f0..9bb3969 100755 --- a/comps/custom/Breadcrumb.tsx +++ b/comps/custom/Breadcrumb.tsx @@ -50,6 +50,9 @@ export const Breadcrumb: FC = ({ value, className }) => { ) : ( <> + {(!local.value || local.value.length === 0) && + isEditor && + "Breadcrumb"} {(Array.isArray(local.value) ? local.value : []).map( (cur, index): ReactNode => { const lastIndex = (local.value || []).length - 1; diff --git a/comps/form/Form.tsx b/comps/form/Form.tsx index bbea662..b75d8ff 100755 --- a/comps/form/Form.tsx +++ b/comps/form/Form.tsx @@ -19,6 +19,7 @@ export const Form: FC = (props) => { data: editorFormData[props.item.id] ? editorFormData[props.item.id].data : {}, + deps: {}, status: "init", reload: async () => { formReload(fm); @@ -130,34 +131,33 @@ export const Form: FC = (props) => { } }, [getPathname()]); - useEffect( - () => { - if (fm.status === "ready") { - fm.status = "init"; - fm.render(); + useEffect(() => { + if (fm.status === "ready") { + fm.status = "init"; + fm.render(); + } + + let i = 0; + const ival = setInterval(() => { + const old_has_fields_container = fm.has_fields_container; + if (form_inner_ref.current?.querySelector(".form-fields")) { + fm.has_fields_container = true; + } else { + fm.has_fields_container = false; } - let i = 0; - const ival = setInterval(() => { - const old_has_fields_container = fm.has_fields_container; - if (form_inner_ref.current?.querySelector(".form-fields")) { - fm.has_fields_container = true; - } else { - fm.has_fields_container = false; - } + if (old_has_fields_container !== fm.has_fields_container) { + fm.render(); + clearInterval(ival); + } + if (i > 20) { + clearInterval(ival); + } + i++; + }, 10); + }, Object.values(props.deps) || []); - if (old_has_fields_container !== fm.has_fields_container) { - fm.render(); - clearInterval(ival); - } - if (i > 20) { - clearInterval(ival); - } - i++; - }, 10); - }, - Array.isArray(props.on_load_deps) ? props.on_load_deps : [] - ); + fm.deps = props.deps; if (fm.status === "init") { formInit(fm, props); diff --git a/comps/form/field/type/TypeLink.tsx b/comps/form/field/type/TypeLink.tsx index 86997e5..1c59258 100755 --- a/comps/form/field/type/TypeLink.tsx +++ b/comps/form/field/type/TypeLink.tsx @@ -1,3 +1,5 @@ +import { BreadItem } from "lib/comps/custom/Breadcrumb"; +import { MDLocal } from "lib/comps/md/utils/typings"; import { FieldLoading, Spinner } from "lib/comps/ui/field-loading"; import { hashSum } from "lib/utils/hash-sum"; import { getPathname } from "lib/utils/pathname"; @@ -6,6 +8,8 @@ import { ArrowUpRight } from "lucide-react"; import { FC, ReactNode, useEffect } from "react"; import { FMLocal, FieldLocal, FieldProp } from "../../typings"; +const link_cache = {} as Record; + export const FieldLink: FC<{ field: FieldLocal; fm: FMLocal; @@ -14,8 +18,8 @@ export const FieldLink: FC<{ const local = useLocal({ text: "", init: false, - navigating: false, custom: false, + md: null as null | MDLocal, }); const Link = ({ @@ -23,10 +27,13 @@ export const FieldLink: FC<{ }: { children: (arg: { icon: any }) => ReactNode; }) => { + const link_local = useLocal({ + navigating: false, + }); return (
{ if (!isEditor) { - local.navigating = true; - local.render(); - if (!(await navigateLink(arg.link, field))) { - local.navigating = false; - local.render(); + link_local.navigating = true; + link_local.render(); + if (!(await navigateLink(arg.link, field, fm.deps.md))) { + link_local.navigating = false; + link_local.render(); } } }} > {children({ - icon: local.navigating ? ( + icon: link_local.navigating ? ( +
{!local.init ? ( ) : ( - - {({ icon }) => { - return ( - <> -
{local.text}
- {icon} - - ); - }} - + <> + {local.custom ? ( + local.text + ) : ( + + {({ icon }) => { + return ( + <> +
{local.text}
+ {icon} + + ); + }} + + )} + )}
); @@ -110,6 +125,8 @@ export const FieldLink: FC<{ export type LinkParam = { url: string; where: any; + create: any; + update: any; hash: any; prefix: { label: any; @@ -118,8 +135,12 @@ export type LinkParam = { }[]; }; -const navigateLink = async (link: FieldProp["link"], field: FieldLocal) => { - let params = link.params(field); +const navigateLink = async ( + link: FieldProp["link"], + field: FieldLocal, + md?: MDLocal +) => { + let params = link.params(field) as { where: any; create: any; update: any }; if (typeof params === "object") { if (params instanceof Promise) { @@ -127,17 +148,19 @@ const navigateLink = async (link: FieldProp["link"], field: FieldLocal) => { } } - const md = params.md; - const where = params.where; - const prefix: LinkParam["prefix"] = []; if (md) { - md.header.render(); - if (md.header.breadcrumb.length > 0) { + let bread: BreadItem[] = []; + if (md.selected && md.header.child.breadcrumb) { + bread = md.header.child.breadcrumb(); + } else if (!md.selected && md.header.master.breadcrumb) { + bread = md.header.master.breadcrumb(); + } + if (bread.length > 0) { const path = getPathname({ hash: false }); let i = 0; - for (const b of md.header.breadcrumb) { + for (const b of bread) { prefix.push({ label: b.label, url: `${path}`, @@ -152,14 +175,16 @@ const navigateLink = async (link: FieldProp["link"], field: FieldLocal) => { } const values: LinkParam = { + ...params, url: getPathname({ hash: false }), - where, prefix, hash: "", }; const vhash = hashSum(values); values.hash = vhash; + link_cache[vhash] = values; + if (!link.url) { alert("No URL defined!"); return false; @@ -187,10 +212,21 @@ export const parseLink = () => { } return []; }; + export const fetchLinkParams = async ( parsed_link?: ReturnType ) => { const parsed = parsed_link || parseLink(); - return await Promise.all(parsed.map((e) => api._kv("get", e))); + return await Promise.all( + parsed.map(async (e) => { + if (link_cache[e]) { + return link_cache[e]; + } + + const result = await api._kv("get", e); + link_cache[e] = result; + return result; + }) + ); }; diff --git a/comps/form/gen/fields.ts b/comps/form/gen/fields.ts index ea81e98..517ef20 100755 --- a/comps/form/gen/fields.ts +++ b/comps/form/gen/fields.ts @@ -218,9 +218,10 @@ export const newField = async ( return v; }) ); + let type = "multi-option"; let sub_type = "typeahead"; - if (field.relation?.fields?.length > 2) { + if (field.relation?.fields.filter((e) => !e.is_pk)?.length >= 2) { sub_type = "table-edit"; child = createItem({ childs: await generateRelation( @@ -233,15 +234,115 @@ export const newField = async ( false ), }); + } else { + type = "link"; } + + let label = formatName(field.name); + let link_params = { where: "", create: "", update: "" }; + let rel = field.relation; + if (type === "link" && field.relation) { + const rels = field.relation.fields.filter((e) => e.relation); + if (rels.length === 1) { + rel = rels[0].relation as any; + label = formatName(rel.to.table); + + link_params = { + where: `{ + "${rel.from.table}": { + some: { + "${field.relation.to.fields[0]}": fm.data["${field.relation.from.fields[0]}"], + } + } + } as Prisma.${rel.to.table}WhereInput`, + create: `{ + "${rel.from.table}": { + create: { + "${field.relation.to.fields[0]}": fm.data["${field.relation.from.fields[0]}"] + } + }, + } as Prisma.${rel.to.table}CreateInput`, + update: `{}`, + }; + } else { + link_params = { + where: `{ + "${rel.to.fields[0]}": fm.data["${field.relation.from.fields[0]}"], + } as Prisma.${rel.to.table}WhereInput`, + create: `{ + "${rel.from.table}": { + connect: { + "${rel.from.fields[0]}": fm.data["${field.relation.from.fields[0]}"] + } + }, + } as Prisma.${rel.to.table}CreateInput`, + update: `{}`, + }; + } + } + return createItem({ component: { id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67", props: { - name: field.name, - label: formatName(field.name), - type: "multi-option", + type, sub_type, + name: field.name, + label, + link__text: [ + ` +({ Link }) => { + const rel = fm.data["${field.name}"]; + return ( + <> + {Array.isArray(rel) && ( +
+ {rel.length === 0 ? "No" : rel.length}{" "} + {rel.length > 1 ? "items" : "item"} +
+ )} + + {({ icon }) => { + return ( + <> +
Detail
{icon} + + ); + }} + + + ); +}`, + `({ Link }) => { + const rel = fm.data["${field.name}"]; + return (React.createElement(React.Fragment, null, + Array.isArray(rel) && (React.createElement("div", { className: cx("flex items-center border-r", css\`padding:0px 10px 0px 5px;margin-right:10px;\`) }, + rel.length === 0 ? "No" : rel.length, + " ", + rel.length > 1 ? "items" : "item")), + React.createElement(Link, null, ({ icon }) => { + return (React.createElement(React.Fragment, null, + React.createElement("div", null, "Detail"), + " ", + icon)); + }))); +}; +`, + ], + link__params: [ + `async (field: any) => { + return { + where: ${link_params.where}, + create: ${link_params.create}, + update: ${link_params.update} + }; +}`, + ], rel__gen_table: field.name, opt__on_load: [result.on_load], ext__show_label: show ? "y" : "n", diff --git a/comps/form/gen/gen-form.ts b/comps/form/gen/gen-form.ts index 1c63727..b482a19 100755 --- a/comps/form/gen/gen-form.ts +++ b/comps/form/gen/gen-form.ts @@ -207,14 +207,8 @@ ${ `\ if (typeof md !== "undefined") { fm.status = "ready"; - // kembali ke tabel - setTimeout(() => { - md.selected = null; - md.tab.active = "master"; - md.internal.action_should_refresh = true; - md.params.apply(); - md.render(); - }, 500); + md.render(); + fm.render(); }` } } catch (e) { diff --git a/comps/form/gen/gen-rel-many.ts b/comps/form/gen/gen-rel-many.ts index 4495bae..440302a 100755 --- a/comps/form/gen/gen-rel-many.ts +++ b/comps/form/gen/gen-rel-many.ts @@ -210,6 +210,7 @@ export const genRelMany = (prop: { } `; } + return result; }; export const getColumn = (data: any) => { diff --git a/comps/form/gen/gen-table-edit.ts b/comps/form/gen/gen-table-edit.ts index 3e078d9..2838fc3 100755 --- a/comps/form/gen/gen-table-edit.ts +++ b/comps/form/gen/gen-table-edit.ts @@ -306,7 +306,7 @@ export const genTableEdit = async ( }, }); const child_sub_name = createItem({ - name: "tbl-col", + name: "table: columns", childs: childs, }); if (commit) { diff --git a/comps/form/typings.ts b/comps/form/typings.ts index ae4b09c..da05b64 100755 --- a/comps/form/typings.ts +++ b/comps/form/typings.ts @@ -17,7 +17,7 @@ export type FMProps = { label_width: number; gen_fields: any; gen_table: string; - on_load_deps?: any[]; + deps?: any; feature?: any[]; sfd_field?: any; render_parent?: () => void; @@ -52,12 +52,10 @@ export type FieldProp = { | string | ((arg: { field: FieldLocal; - Link: FC<{ children: any; }>; + Link: FC<{ children: any }>; }) => Promise | string); url: string; - params: ( - field: FieldLocal - ) => { md: MDLocal; where: any } | Promise<{ md: MDLocal; where: any }>; + params: (field: FieldLocal) => { where: any } | Promise<{ where: any }>; }; fm: FMLocal; type: FieldType | (() => FieldType); @@ -119,6 +117,7 @@ export type FieldProp = { export type FMInternal = { status: "init" | "resizing" | "loading" | "saving" | "ready"; data: any; + deps: any; reload: () => Promise; submit: () => Promise; events: { diff --git a/comps/form/utils/init.tsx b/comps/form/utils/init.tsx index ae22ea9..af6469e 100755 --- a/comps/form/utils/init.tsx +++ b/comps/form/utils/init.tsx @@ -118,9 +118,32 @@ export const formInit = (fm: FMLocal, props: FMProps) => { ); } + + const form = JSON.parse(JSON.stringify(fm.data)); + + if (fm.deps.md) { + const md = fm.deps.md; + const last = md.params.links[md.params.links.length - 1]; + if (last) { + const pk = Object.values(fm.field_def).find((e) => e.is_pk); + if (pk) { + let obj = last.update; + if (!fm.data[pk.name]) { + obj = last.create; + } + + if (typeof obj === "object" && obj) { + for (const [k, v] of Object.entries(obj)) { + form[k] = v; + } + } + } + } + } + const success = await fm.props.on_submit({ fm, - form: fm.data, + form, error: fm.error.object, }); diff --git a/comps/list/TableList.tsx b/comps/list/TableList.tsx index 42f157f..56969c9 100755 --- a/comps/list/TableList.tsx +++ b/comps/list/TableList.tsx @@ -29,6 +29,7 @@ import { getFilter } from "../filter/utils/get-filter"; import { Skeleton } from "../ui/skeleton"; import { sortTree } from "./utils/sort-tree"; import { set } from "lib/utils/set"; +import { MDLocal } from "../md/utils/typings"; type OnRowClick = (arg: { row: any; @@ -70,6 +71,7 @@ type TableListProp = { gen_table?: string; softdel_type?: string; cache_row?: boolean; + md?: MDLocal; }; const w = window as any; const selectCellClassname = css` @@ -102,6 +104,7 @@ export const TableList: FC = ({ value, cache_row, __props, + md, }) => { if (mode === "auto") { if (w.isMobile) { @@ -232,6 +235,15 @@ export const TableList: FC = ({ const orderBy = local.sort.orderBy || undefined; const where = filterWhere(filter_name, __props); + if (md) { + const last = md.params.links[md.params.links.length - 1]; + if (last && last.where) { + for (const [k, v] of Object.entries(last.where)) { + where[k] = v; + } + } + } + call_prasi_events("tablelist", "where", [__props?.gen__table, where]); const load_args: any = { @@ -290,14 +302,14 @@ export const TableList: FC = ({ ); let childs: any[] = []; - let sub_name = "fields"; + let sub_name = ["fields"]; switch (mode) { case "table": - sub_name = "tbl-col"; + sub_name = ["tbl-col", "table: columns"]; break; case "list": - sub_name = "list-row"; + sub_name = ["list-row", "list: rows"]; break; } @@ -344,7 +356,7 @@ export const TableList: FC = ({ } }; const mode_child = raw_childs.find( - (e: any) => e.name === sub_name || e.name === mode + (e: any) => sub_name.includes(e.name) || e.name === mode ); if (mode_child) { const tbl = _item.edit.childs[0].edit.childs.find( diff --git a/comps/md/MasterDetail.tsx b/comps/md/MasterDetail.tsx index 0b4b14b..dccc863 100755 --- a/comps/md/MasterDetail.tsx +++ b/comps/md/MasterDetail.tsx @@ -93,7 +93,8 @@ export const MasterDetail: FC = (arg) => { if (pk) { const value = md.params.hash[md.name]; if (value) { - if (!md.selected) { + if (md.selected && md.selected[pk.name] === value) { + } else { md.selected = { [pk.name]: value }; } const tab = md.params.tabs[md.name]; diff --git a/comps/md/gen/md-form.ts b/comps/md/gen/md-form.ts index ab982c2..c3af6a9 100755 --- a/comps/md/gen/md-form.ts +++ b/comps/md/gen/md-form.ts @@ -25,6 +25,10 @@ export const generateMDForm = async ( mode: "raw", value: `${JSON.stringify(arg.fields)}`, }, + deps: { + mode: "raw", + value: `({ md: typeof md !== "undefined" ? md : undefined })`, + }, on_load: { mode: "string", value: "", diff --git a/comps/md/gen/mode-table-list.ts b/comps/md/gen/mode-table-list.ts index 7e1dd7e..988cf12 100755 --- a/comps/md/gen/mode-table-list.ts +++ b/comps/md/gen/mode-table-list.ts @@ -1,11 +1,11 @@ export const modeTableList = (mode: string) => { - let sub_name = "tbl-col"; + let sub_name = "table: columns"; switch (mode) { case "table": - sub_name = "tbl-col"; + sub_name = "table: columns"; break; case "list": - sub_name = "list-row"; + sub_name = "list: rows"; break; } return sub_name; diff --git a/comps/md/parts/MDMaster.tsx b/comps/md/parts/MDMaster.tsx index 8cc1737..de19b9f 100755 --- a/comps/md/parts/MDMaster.tsx +++ b/comps/md/parts/MDMaster.tsx @@ -21,6 +21,7 @@ export const MDRenderMaster: FC<{ md.header.master.breadcrumb = breadcrumb; useEffect(() => { + md.header.render(); if (md) { let width = 0; let min_width = 0; diff --git a/gen/prop/gen_prop_fields.ts b/gen/prop/gen_prop_fields.ts index 2d8dd0f..87c53f0 100755 --- a/gen/prop/gen_prop_fields.ts +++ b/gen/prop/gen_prop_fields.ts @@ -79,8 +79,8 @@ const get_layer = async ( default: v.default, }), label: k + (!v.optional && !v.default ? " *" : ""), - alt: v.type, - checked: v.is_pk && depth === 0, + alt: `${v.is_pk ? "🔑" : ""} ${v.type}`, + checked: v.is_pk, }); } }