diff --git a/comps/form/Form.tsx b/comps/form/Form.tsx index 1f59e57..0c4ab4f 100755 --- a/comps/form/Form.tsx +++ b/comps/form/Form.tsx @@ -32,6 +32,11 @@ export const Form: FC = (props) => { promises: [], done: [], }, + submit: { + timeout: null as any, + promises: [], + done: [], + }, }, field_def: {}, props: {} as any, @@ -90,11 +95,6 @@ export const Form: FC = (props) => { } const toaster_el = document.getElementsByClassName("prasi-toaster")[0]; - const childs = get( - body, - "props.meta.item.component.props.body.content.childs" - ) as any[]; - return (
{ diff --git a/comps/form/field/type/TypeCustom.tsx b/comps/form/field/type/TypeCustom.tsx index 21d83db..25bb27b 100755 --- a/comps/form/field/type/TypeCustom.tsx +++ b/comps/form/field/type/TypeCustom.tsx @@ -7,20 +7,47 @@ export const TypeCustom: FC<{ field: FieldLocal; fm: FMLocal }> = ({ field, fm, }) => { - const local = useLocal({ custom: null as any }, async () => { - if (field.custom) { - local.custom = await field.custom(); - local.render(); - } + const local = useLocal({ + custom: null as any, + exec: false, + result: null as any, }); + if (!local.custom && field.custom) { + console.log("field", field.custom); + local.custom = field.custom; + } + + if (!local.exec) { + local.exec = true; + const callback = (value: any, should_render: boolean) => { + local.result = value; + if (should_render) { + local.render(); + setTimeout(() => { + local.exec = false; + }, 100); + } + }; + if (field.custom) { + const res = local.custom(); + if (res instanceof Promise) { + res.then((value) => { + callback(value, true); + }); + } else { + callback(res, false); + } + } + } + let el = null as any; - if (local.custom) { - if (isValidElement(local.custom)) { - el = local.custom; + if (local.result) { + if (isValidElement(local.result)) { + el = local.result; } else { - if (local.custom.field === "text") { - el = ; + if (local.result.field === "text") { + el = ; } } } diff --git a/comps/form/field/type/TypeText.tsx b/comps/form/field/type/TypeText.tsx index 9f580df..7955926 100755 --- a/comps/form/field/type/TypeText.tsx +++ b/comps/form/field/type/TypeText.tsx @@ -1,21 +1,34 @@ import { FC } from "react"; import { FMLocal, FieldLocal } from "../../typings"; import { useLocal } from "@/utils/use-local"; +import parser from "any-date-parser"; export type PropTypeText = { - type: "text" | "password" | "number"; + type: "text" | "password" | "number" | "date" | "datetime"; }; +const parse = parser.exportAsFunctionAny("en-US"); + export const FieldTypeText: FC<{ field: FieldLocal; fm: FMLocal; prop: PropTypeText; }> = ({ field, fm, prop }) => { const input = useLocal({}); - const value = fm.data[field.name]; + let value: any = fm.data[field.name]; field.input = input; field.prop = prop; + if (["date", "datetime"].includes(prop.type)) { + if (typeof value === "string") { + let date = parse(value); + if (typeof date === "object" && date instanceof Date) { + if (prop.type === "date") value = date.toISOString().substring(0, 10); + else if (prop.type === "datetime") value = date.toISOString(); + } + } + } + return ( <> Promise; - submit: () => Promise; + submit: () => Promise; events: { on_change: (name: string, new_value: any) => void; }; @@ -82,6 +82,11 @@ export type FMInternal = { promises: Promise[]; done: any[]; }; + submit: { + promises: Promise[]; + timeout: ReturnType; + done: any[]; + }; }; props: Exclude; size: { @@ -204,6 +209,6 @@ export type CustomField = | { field: "relation"; type: "has-many" | "has-one" }; export const FieldTypeCustom = `type CustomField = - { field: "text", type: "text" | "password" | "number" } + { field: "text", type: "text" | "password" | "number" | "date" | "datetime" } | { field: "relation", type: "has-many" | "has-one" } `; diff --git a/comps/form/utils/init.tsx b/comps/form/utils/init.tsx index 2125224..85d2ff2 100755 --- a/comps/form/utils/init.tsx +++ b/comps/form/utils/init.tsx @@ -1,6 +1,6 @@ import { parseGenField } from "@/gen/utils"; import get from "lodash.get"; -import { Loader2 } from "lucide-react"; +import { AlertTriangle, Check, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { FMLocal, FMProps } from "../typings"; import { editorFormData } from "./ed-data"; @@ -89,10 +89,77 @@ export const formInit = (fm: FMLocal, props: FMProps) => { return promise; }; - fm.submit = async () => { - if (typeof fm.props.on_submit === "function") { - fm.props.on_submit({ fm, form: fm.data, error: fm.error.object }); - } + fm.submit = () => { + const promise = new Promise(async (done) => { + fm.internal.submit.done.push(done); + clearTimeout(fm.internal.submit.timeout); + fm.internal.submit.timeout = setTimeout(async () => { + const done_all = (val: boolean) => { + for (const d of fm.internal.submit.done) { + d(val); + } + fm.internal.submit.done = []; + fm.render(); + }; + + if (typeof fm.props.on_submit === "function") { + if (fm.props.sonar === "on") { + toast.loading( + <> + + Submitting... + + ); + } + const success = await fm.props.on_submit({ + fm, + form: fm.data, + error: fm.error.object, + }); + + toast.dismiss(); + done_all(success); + if (fm.props.sonar === "on") { + setTimeout(() => { + toast.dismiss(); + + if (!success) { + toast.error( +
+ + Save Failed, please correct{" "} + {Object.keys(fm.error.list).length} errors. +
, + { + dismissible: true, + className: css` + background: #ffecec; + border: 2px solid red; + `, + } + ); + } else { + toast.success( +
+ + Done +
, + { + className: css` + background: #e4ffed; + border: 2px solid green; + `, + } + ); + } + }, 100); + } + } + }, 100); + }); + fm.internal.submit.promises.push(promise); + + return promise; }; if (typeof fm.props.on_init === "function") { fm.props.on_init({ fm, submit: fm.submit, reload: fm.reload });