This commit is contained in:
rizky 2024-04-15 01:21:16 -07:00
parent 9cde4fb165
commit ad557ca5c0
5 changed files with 136 additions and 24 deletions

View File

@ -32,6 +32,11 @@ export const Form: FC<FMProps> = (props) => {
promises: [],
done: [],
},
submit: {
timeout: null as any,
promises: [],
done: [],
},
},
field_def: {},
props: {} as any,
@ -90,11 +95,6 @@ export const Form: FC<FMProps> = (props) => {
}
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
const childs = get(
body,
"props.meta.item.component.props.body.content.childs"
) as any[];
return (
<form
onSubmit={(e) => {

View File

@ -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 = <FieldTypeText field={field} fm={fm} prop={local.custom} />;
if (local.result.field === "text") {
el = <FieldTypeText field={field} fm={fm} prop={local.result} />;
}
}
}

View File

@ -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 (
<>
<input

View File

@ -63,7 +63,7 @@ export type FMInternal = {
status: "init" | "resizing" | "loading" | "saving" | "ready";
data: any;
reload: () => Promise<void>;
submit: () => Promise<void>;
submit: () => Promise<boolean>;
events: {
on_change: (name: string, new_value: any) => void;
};
@ -82,6 +82,11 @@ export type FMInternal = {
promises: Promise<void>[];
done: any[];
};
submit: {
promises: Promise<boolean>[];
timeout: ReturnType<typeof setTimeout>;
done: any[];
};
};
props: Exclude<FMProps, "body" | "PassProp">;
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" }
`;

View File

@ -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<boolean>(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(
<>
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
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(
<div className="c-flex c-text-red-600 c-items-center">
<AlertTriangle className="c-h-4 c-w-4 c-mr-1" />
Save Failed, please correct{" "}
{Object.keys(fm.error.list).length} errors.
</div>,
{
dismissible: true,
className: css`
background: #ffecec;
border: 2px solid red;
`,
}
);
} else {
toast.success(
<div className="c-flex c-text-green-700 c-items-center">
<Check className="c-h-4 c-w-4 c-mr-1 " />
Done
</div>,
{
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 });