From 26c1402bd1d69730adb5119a20865ec036d621f7 Mon Sep 17 00:00:00 2001 From: rizky Date: Fri, 12 Apr 2024 23:12:08 -0700 Subject: [PATCH] fix --- comps/form/Form.tsx | 13 +-- comps/form/field/Field.tsx | 1 + comps/form/field/FieldInput.tsx | 45 +++++--- comps/form/field/mapping.ts | 35 ++++-- comps/form/field/raw/Dropdown.tsx | 150 +++++++++++++++++++++++++ comps/form/field/type/TypeRelation.tsx | 36 ++++++ comps/form/field/type/TypeText.tsx | 15 ++- comps/form/typings.ts | 53 +++++---- comps/form/utils/gen-mitem.ts | 24 +++- comps/form/utils/init.tsx | 13 ++- comps/form/utils/use-field.tsx | 3 +- data.ts | 1 + gen/gen_form/gen_form.ts | 9 +- gen/gen_form/new_field.ts | 40 +++++-- 14 files changed, 359 insertions(+), 79 deletions(-) create mode 100755 comps/form/field/raw/Dropdown.tsx create mode 100755 comps/form/field/type/TypeRelation.tsx diff --git a/comps/form/Form.tsx b/comps/form/Form.tsx index ae80dea..e19c9dc 100755 --- a/comps/form/Form.tsx +++ b/comps/form/Form.tsx @@ -1,14 +1,12 @@ import { useLocal } from "@/utils/use-local"; -import { FC, Fragment, useEffect, useRef } from "react"; -import { FMInternal, FMProps } from "./typings"; -import { formReload } from "./utils/reload"; -import { formInit } from "./utils/init"; +import get from "lodash.get"; +import { FC, useRef } from "react"; import { createPortal } from "react-dom"; import { Toaster } from "sonner"; -import get from "lodash.get"; -import { Field } from "./field/Field"; -import { getProp } from "../../.."; +import { FMInternal, FMProps } from "./typings"; import { editorFormData } from "./utils/ed-data"; +import { formInit } from "./utils/init"; +import { formReload } from "./utils/reload"; const editorFormWidth = {} as Record; @@ -35,6 +33,7 @@ export const Form: FC = (props) => { done: [], }, }, + field_def: {}, props: {} as any, size: { width: editorFormWidth[props.item.id] diff --git a/comps/form/field/Field.tsx b/comps/form/field/Field.tsx index e2b1b1d..ce29a74 100755 --- a/comps/form/field/Field.tsx +++ b/comps/form/field/Field.tsx @@ -36,6 +36,7 @@ export const Field: FC = (arg) => { w === "auto" && fm.size.field === "full" && "c-w-full", w === "auto" && fm.size.field === "half" && "c-w-1/2", w === "full" && "c-w-full", + w === "¾" && "c-w-3/4", w === "½" && "c-w-1/2", w === "⅓" && "c-w-1/3", w === "¼" && "c-w-1/4", diff --git a/comps/form/field/FieldInput.tsx b/comps/form/field/FieldInput.tsx index 8c62128..eb75d51 100755 --- a/comps/form/field/FieldInput.tsx +++ b/comps/form/field/FieldInput.tsx @@ -19,7 +19,7 @@ export const FieldInput: FC<{ _item: any; _meta: any; _sync: (mitem: any, item: any) => void; -}> = ({ field, fm, PassProp, child, _meta, _item }) => { +}> = ({ field, fm, PassProp, child, _meta, _item, _sync }) => { const prefix = typeof field.prefix === "function" ? field.prefix() : null; const suffix = typeof field.suffix === "function" ? field.suffix() : null; const errors = fm.error.get(field.name); @@ -31,26 +31,35 @@ export const FieldInput: FC<{ let found = null as any; if (childs && childs.length > 0) { for (const child of childs) { - if (child.component?.id === fieldMapping[field.type].id) { + const mp = (fieldMapping as any)[field.type]; + if (child.component?.id === mp.id) { found = child; - const item = createItem({ - component: { id: "--", props: fieldMapping[field.type].props }, - }); + if (mp.props) { + const item = createItem({ + component: { + id: "--", + props: + typeof mp.props === "function" ? mp.props(fm, field) : mp.props, + }, + }); - 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; - } else { - props[k] = v; - should_update = true; + 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; + } else { + if (field.prop && !field.prop[k]) { + props[k] = v; + should_update = true; + } + } } - } - if (should_update) { - updateFieldMItem(_meta, found); + if (should_update) { + updateFieldMItem(_meta, found, _sync); + } } } } @@ -58,14 +67,14 @@ export const FieldInput: FC<{ useEffect(() => { if (isEditor && !found) { - genFieldMitem({ _meta, _item, field, fm }); + genFieldMitem({ _meta, _item, _sync, field, fm }); } }, []); return (
= { +export const fieldMapping: { + [K in FieldProp["type"]]: { + id: string; + props?: + | Record + | (( + fm: FMLocal, + field: FieldInternal & { + render: () => void; + } + ) => Record); + }; +} = { text: { id: "ca7ac237-8f22-4492-bb9d-4b715b1f5c25", props: { type: "text" } }, - number: { - id: "ca7ac237-8f22-4492-bb9d-4b715b1f5c25", - props: { type: "number" }, + relation: { + id: "69263ca0-61a1-4899-ad5f-059ac12b94d1", + props: (fm, field) => { + const rel = fm.field_def[field.name]; + if (rel) { + if (field.prop && !field.prop.type) { + return { type: rel.type }; + } + } + return {}; + }, }, -} as any; +}; diff --git a/comps/form/field/raw/Dropdown.tsx b/comps/form/field/raw/Dropdown.tsx new file mode 100755 index 0000000..a375c95 --- /dev/null +++ b/comps/form/field/raw/Dropdown.tsx @@ -0,0 +1,150 @@ +import { Popover } from "@/comps/custom/Popover"; +import { useLocal } from "@/utils/use-local"; +import { ChevronDown } from "lucide-react"; +import { FC, ReactNode } from "react"; + +type OptionItem = { value: string; label: string; el?: ReactNode }; + +export const RawDropdown: FC<{ + options: OptionItem[]; + className?: string; + value: string; + onFocus?: () => void; + onBlur?: () => void; + onChange?: (value: string) => void; +}> = ({ value, options, className, onFocus, onBlur, onChange }) => { + const local = useLocal({ + open: false, + input: { + value: "", + el: null as any, + }, + filter: "", + width: 0, + selected: undefined as undefined | OptionItem, + }); + + let filtered = options; + + if (local.filter) { + filtered = options.filter((e) => { + if (e.label.toLowerCase().includes(local.filter)) return true; + return false; + }); + } + local.selected = options.find((e) => e.value === value); + + return ( + { + local.open = false; + local.render(); + }} + arrow={false} + className={cx("c-rounded-sm c-bg-white")} + content={ +
+ <> + {filtered.map((item, idx) => { + return ( +
0 && "c-border-t", + idx === 0 && "c-rounded-t-sm", + idx === filtered.length - 1 && "c-rounded-b-sm" + )} + onClick={() => { + if (onChange) onChange(item.value); + }} + > + {item.label} +
+ ); + })} + +
+ } + > +
{ + local.open = true; + if (local.selected) local.input.value = local.selected.label; + local.filter = ""; + local.render(); + setTimeout(() => { + local.input.el?.focus(); + }); + }} + ref={(el) => { + if (local.width === 0 && el) { + const box = el.getBoundingClientRect(); + if (box && box.width) { + local.width = box.width; + local.render(); + } + } + }} + > +
+ { + local.input.value = e.currentTarget.value; + local.filter = local.input.value.toLowerCase(); + local.render(); + }} + ref={(el) => { + local.input.el = el; + }} + type="text" + onFocus={onFocus} + onBlur={onBlur} + /> + + {!local.open && local.selected && ( +
+ {local.selected.el || local.selected.label} +
+ )} +
+
+ +
+
+
+ ); +}; diff --git a/comps/form/field/type/TypeRelation.tsx b/comps/form/field/type/TypeRelation.tsx new file mode 100755 index 0000000..743d583 --- /dev/null +++ b/comps/form/field/type/TypeRelation.tsx @@ -0,0 +1,36 @@ +import { useLocal } from "@/utils/use-local"; +import { FC } from "react"; +import { FMLocal, FieldLocal } from "../../typings"; +import { RawDropdown } from "../raw/Dropdown"; + +export type PropTypeRelation = { + type: "has-one" | "has-many"; +}; +export const FieldTypeRelation: FC<{ + field: FieldLocal; + fm: FMLocal; + prop: PropTypeRelation; +}> = ({ field, fm, prop }) => { + const input = useLocal({}); + const value = fm.data[field.name]; + field.input = input; + field.prop = prop; + + return ( + <> + { + field.focused = true; + field.render(); + }} + onBlur={() => { + field.focused = false; + field.render(); + }} + /> + + ); +}; diff --git a/comps/form/field/type/TypeText.tsx b/comps/form/field/type/TypeText.tsx index 7685368..27b060f 100755 --- a/comps/form/field/type/TypeText.tsx +++ b/comps/form/field/type/TypeText.tsx @@ -1,14 +1,21 @@ import { FC } from "react"; import { FMLocal, FieldLocal } from "../../typings"; +import { useLocal } from "@/utils/use-local"; + +export type PropTypeText = { + type: "text" | "password" | "number"; +}; export const FieldTypeText: FC<{ field: FieldLocal; fm: FMLocal; - prop: { - type: "text" | "password" | "number"; - }; + prop: PropTypeText; }> = ({ field, fm, prop }) => { + const input = useLocal({}); const value = fm.data[field.name]; + field.input = input; + field.prop = prop; + return ( { field.focused = true; diff --git a/comps/form/typings.ts b/comps/form/typings.ts index b222ff5..d22c0ba 100755 --- a/comps/form/typings.ts +++ b/comps/form/typings.ts @@ -1,8 +1,10 @@ +import { GFCol } from "@/gen/utils"; import { ReactNode } from "react"; -import { SliderOptions } from "../form-old/Slider/types"; import { FieldOptions } from "../form-old/type"; import { FormHook } from "../form-old/utils/utils"; import { editorFormData } from "./utils/ed-data"; +import { PropTypeText } from "./field/type/TypeText"; +import { PropTypeRelation } from "./field/type/TypeRelation"; export type FMProps = { on_init: (arg: { fm: FMLocal; submit: any; reload: any }) => any; @@ -18,6 +20,7 @@ export type FMProps = { item: any; label_mode: "vertical" | "horizontal" | "hidden"; label_width: number; + gen_fields: any; }; export type FieldProp = { @@ -26,20 +29,18 @@ export type FieldProp = { desc?: string; props?: any; fm: FMLocal; - type: - | "text" - | "number" - | "textarea" - | "dropdown" - | "relation" - | "password" - | "radio" - | "date" - | "datetime" - | "money" - | "slider" - | "master-link" - | "custom"; + type: "text" | "relation"; + // | "number" + // | "textarea" + // | "dropdown" + // | "password" + // | "radio" + // | "date" + // | "datetime" + // | "money" + // | "slider" + // | "master-link" + // | "custom"; required: "y" | "n"; required_msg: (name: string) => string; options: FieldOptions; @@ -50,7 +51,7 @@ export type FieldProp = { selection: "single" | "multi"; prefix: any; suffix: any; - width: "auto" | "full" | "½" | "⅓" | "¼"; + width: "auto" | "full" | "¾" | "½" | "⅓" | "¼"; _meta: any; _item: any; _sync: any; @@ -65,6 +66,7 @@ export type FMInternal = { on_change: (name: string, new_value: any) => void; }; fields: Record; + field_def: Record; error: { readonly list: { name: string; error: string[] }[]; set: (name: string, error: string[]) => void; @@ -87,10 +89,16 @@ export type FMInternal = { }; export type FMLocal = FMInternal & { render: () => void }; -export type FieldInternal = { +type FieldInternalProp = { + text: PropTypeText; + number: PropTypeText; + relation: PropTypeRelation; +}; + +export type FieldInternal = { status: "init" | "loading" | "ready"; name: FieldProp["name"]; - type: FieldProp["type"]; + type: T; label: FieldProp["label"]; desc: FieldProp["desc"]; prefix: FieldProp["prefix"]; @@ -100,9 +108,16 @@ export type FieldInternal = { focused: boolean; disabled: boolean; required_msg: FieldProp["required_msg"]; + col?: GFCol; Child: () => ReactNode; + input: Record & { + render: () => void; + }; + prop?: FieldInternalProp[T]; +}; +export type FieldLocal = FieldInternal & { + render: () => void; }; -export type FieldLocal = FieldInternal & { render: () => void }; export const formType = (active: { item_id: string }, meta: any) => { let data = "null as any"; diff --git a/comps/form/utils/gen-mitem.ts b/comps/form/utils/gen-mitem.ts index 031ea25..be94adc 100755 --- a/comps/form/utils/gen-mitem.ts +++ b/comps/form/utils/gen-mitem.ts @@ -1,12 +1,17 @@ +import { newField } from "@/gen/gen_form/new_field"; import { FMLocal, FieldLocal } from "../typings"; +import get from "lodash.get"; +import { fieldMapping } from "../field/mapping"; +import { createItem } from "@/gen/utils"; export const genFieldMitem = (arg: { _meta: any; _item: any; + _sync: any; fm: FMLocal; field: FieldLocal; }) => { - const { _meta, _item, fm, field } = arg; + const { _meta, _item, _sync, fm, field } = arg; const m = _meta[_item.id]; if (m) { const mitem = m.mitem; @@ -18,16 +23,27 @@ export const genFieldMitem = (arg: { ?.get("content") ?.get("childs"); - // console.log(field.name, childs); + const col = fm.field_def[field.name]; + if (col) { + const component = fieldMapping[field.type as "text"]; + if (component) { + const item = createItem({ + component: component as any, + }); + + _sync(childs, [...childs.toJSON(), item]); + } + } } } }; -export const updateFieldMItem = (_meta: any, _item: any) => { + +export const updateFieldMItem = (_meta: any, _item: any, _sync: any) => { const m = _meta[_item.id]; if (m) { const mitem = m.mitem; if (mitem) { - + _sync(mitem, _item); } } }; diff --git a/comps/form/utils/init.tsx b/comps/form/utils/init.tsx index d5b5cd9..d1f53d5 100755 --- a/comps/form/utils/init.tsx +++ b/comps/form/utils/init.tsx @@ -1,9 +1,10 @@ +import { parseGenField } from "@/gen/utils"; +import get from "lodash.get"; import { Loader2 } from "lucide-react"; import { toast } from "sonner"; import { FMLocal, FMProps } from "../typings"; -import { formError } from "./error"; import { editorFormData } from "./ed-data"; -import get from "lodash.get"; +import { formError } from "./error"; export const formInit = (fm: FMLocal, props: FMProps) => { for (const [k, v] of Object.entries(props)) { @@ -13,6 +14,14 @@ export const formInit = (fm: FMLocal, props: FMProps) => { const { on_load, sonar } = fm.props; fm.error = formError(fm); + if (isEditor) { + fm.field_def = {}; + const defs = parseGenField(fm.props.gen_fields); + for (const d of defs) { + fm.field_def[d.name] = d; + } + } + fm.reload = () => { fm.status = isEditor ? "ready" : "loading"; fm.render(); diff --git a/comps/form/utils/use-field.tsx b/comps/form/utils/use-field.tsx index 36c92d2..b962860 100755 --- a/comps/form/utils/use-field.tsx +++ b/comps/form/utils/use-field.tsx @@ -3,11 +3,12 @@ import { FMLocal, FieldInternal, FieldProp } from "../typings"; import { useEffect } from "react"; export const useField = (arg: FieldProp) => { - const field = useLocal({ + const field = useLocal>({ status: "init", Child: () => { return {arg.child}; }, + input: {}, } as any); const update_field = { diff --git a/data.ts b/data.ts index ad33027..bc7ecb1 100755 --- a/data.ts +++ b/data.ts @@ -1,4 +1,5 @@ export { FieldTypeText } from "./comps/form/field/type/TypeText"; +export { FieldTypeRelation } from "./comps/form/field/type/TypeRelation"; export { Form } from "@/comps/form/Form"; export { Field } from "@/comps/form/field/Field"; export { formType } from "@/comps/form/typings"; diff --git a/gen/gen_form/gen_form.ts b/gen/gen_form/gen_form.ts index 492aaa7..aa7e91b 100755 --- a/gen/gen_form/gen_form.ts +++ b/gen/gen_form/gen_form.ts @@ -1,9 +1,8 @@ -import get from "lodash.get"; -import { GFCol as Col, GFCol, formatName, parseGenField } from "../utils"; -import { NewFieldArg, newField } from "./new_field"; +import { codeBuild } from "../master_detail/utils"; +import { GFCol, parseGenField } from "../utils"; +import { newField } from "./new_field"; import { on_load } from "./on_load"; import { on_submit } from "./on_submit"; -import { codeBuild } from "../master_detail/utils"; export const gen_form = async (modify: (data: any) => void, data: any) => { const table = JSON.parse(data.gen_table.value); @@ -57,7 +56,7 @@ export const gen_form = async (modify: (data: any) => void, data: any) => { } result["body"] = data["body"]; - result.body.content.childs = fields.map(newField); + result.body.content.childs = fields.filter((e) => !e.is_pk).map(newField); } modify(result); }; diff --git a/gen/gen_form/new_field.ts b/gen/gen_form/new_field.ts index 5825930..78f3159 100755 --- a/gen/gen_form/new_field.ts +++ b/gen/gen_form/new_field.ts @@ -29,23 +29,43 @@ export type NewFieldArg = { }; export const newField = (arg: GFCol) => { + const childs = []; + + let type = "text"; + if (["int", "string"].includes(arg.type)) { + childs.push( + createItem({ + component: { + id: "ca7ac237-8f22-4492-bb9d-4b715b1f5c25", + props: { + type: arg.type === "int" ? "number" : "text", + }, + }, + }) + ); + } else if (["has-many", "has-one"].includes(arg.type)) { + type = "relation"; + childs.push( + createItem({ + component: { + id: "69263ca0-61a1-4899-ad5f-059ac12b94d1", + props: { + type: arg.type, + }, + }, + }) + ); + } + const item = createItem({ component: { id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67", props: { name: arg.name, label: formatName(arg.name), + type, child: { - childs: [ - createItem({ - component: { - id: "ca7ac237-8f22-4492-bb9d-4b715b1f5c25", - props: { - type: "text", - }, - }, - }), - ], + childs, }, }, },