wip form
This commit is contained in:
parent
4a1cdf7fcd
commit
121ec5f0ed
|
|
@ -51,11 +51,13 @@ export const Relation: FC<RelationProps> = ({
|
||||||
const select = {} as any;
|
const select = {} as any;
|
||||||
local.pk_field = "";
|
local.pk_field = "";
|
||||||
for (const f of relation.fields) {
|
for (const f of relation.fields) {
|
||||||
if (f.startsWith("::")) {
|
if (typeof f === "string") {
|
||||||
select[f.substring(2)] = true;
|
if (f.startsWith("::")) {
|
||||||
local.pk_field = f.substring(2);
|
select[f.substring(2)] = true;
|
||||||
} else {
|
local.pk_field = f.substring(2);
|
||||||
select[f] = true;
|
} else {
|
||||||
|
select[f] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let q = {};
|
let q = {};
|
||||||
|
|
@ -0,0 +1,286 @@
|
||||||
|
import { Form as FForm } from "@/comps/ui/form";
|
||||||
|
import { Toaster } from "@/comps/ui/sonner";
|
||||||
|
import { cn } from "@/utils";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Skeleton } from "../ui/skeleton";
|
||||||
|
import { FormHook } from "./utils/utils";
|
||||||
|
|
||||||
|
export const Form: FC<{
|
||||||
|
on_init: (arg: { submit: any; reload: any }) => any;
|
||||||
|
on_load: () => any;
|
||||||
|
on_submit: (arg: { form: any; error: any }) => Promise<any>;
|
||||||
|
body: any;
|
||||||
|
form: FormHook;
|
||||||
|
PassProp: any;
|
||||||
|
cache: () => any;
|
||||||
|
sonar: "on" | "off";
|
||||||
|
layout: "auto" | "1-col" | "2-col";
|
||||||
|
}> = ({
|
||||||
|
on_init,
|
||||||
|
on_load,
|
||||||
|
body,
|
||||||
|
form,
|
||||||
|
PassProp,
|
||||||
|
on_submit,
|
||||||
|
cache,
|
||||||
|
layout: _layout,
|
||||||
|
sonar,
|
||||||
|
}) => {
|
||||||
|
const form_hook = useForm<any>({
|
||||||
|
defaultValues: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const local = useLocal({
|
||||||
|
el: null as any,
|
||||||
|
submit_timeout: null as any,
|
||||||
|
submit_done: [] as any[],
|
||||||
|
layout: "unknown" as "unknown" | "2-col" | "1-col",
|
||||||
|
init: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
form.hook = form_hook;
|
||||||
|
if (!form.cache && typeof cache === "function") {
|
||||||
|
try {
|
||||||
|
form.cache = cache() || {};
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
if (!form.cache) form.cache = {};
|
||||||
|
|
||||||
|
if (!form.validation) {
|
||||||
|
form.validation = {};
|
||||||
|
}
|
||||||
|
if (!form.label) {
|
||||||
|
form.label = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let layout = _layout || "auto";
|
||||||
|
if (layout !== "auto") local.layout = layout;
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
return new Promise<boolean>((done) => {
|
||||||
|
local.submit_done.push(done);
|
||||||
|
const done_all = (val: boolean) => {
|
||||||
|
for (const d of local.submit_done) {
|
||||||
|
d(val);
|
||||||
|
}
|
||||||
|
local.submit_done = [];
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
clearTimeout(local.submit_timeout);
|
||||||
|
local.submit_timeout = setTimeout(async () => {
|
||||||
|
if (sonar === "on") {
|
||||||
|
toast.loading(
|
||||||
|
<>
|
||||||
|
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||||
|
Processing ...
|
||||||
|
</>,
|
||||||
|
{
|
||||||
|
dismissible: true,
|
||||||
|
className: css`
|
||||||
|
background: #e4f7ff;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const data = form.hook.getValues();
|
||||||
|
form.hook.clearErrors();
|
||||||
|
for (const [k, v] of Object.entries(form.validation)) {
|
||||||
|
if (v === "required") {
|
||||||
|
if (!data[k]) {
|
||||||
|
const error = {
|
||||||
|
type: "required",
|
||||||
|
message: `${form.label[k] || k} is required.`,
|
||||||
|
};
|
||||||
|
form.hook.formState.errors[k] = error;
|
||||||
|
form.hook.setError(k, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = on_submit({
|
||||||
|
form: data,
|
||||||
|
error: form.hook.formState.errors,
|
||||||
|
});
|
||||||
|
|
||||||
|
const success = await res;
|
||||||
|
toast.dismiss();
|
||||||
|
done_all(success);
|
||||||
|
if (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(form.hook.formState.errors).length} errors.
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
dismissible: true,
|
||||||
|
className: css`
|
||||||
|
background: #ffecec;
|
||||||
|
border: 2px solid red;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast.success(
|
||||||
|
<div className="c-flex c-text-blue-700 c-items-center">
|
||||||
|
<Check className="c-h-4 c-w-4 c-mr-1 " />
|
||||||
|
Done
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
className: css`
|
||||||
|
background: #e4f5ff;
|
||||||
|
border: 2px solid blue;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!local.init) {
|
||||||
|
local.init = true;
|
||||||
|
on_init({
|
||||||
|
submit,
|
||||||
|
reload: () => {
|
||||||
|
local.init = false;
|
||||||
|
form.unload = () => {
|
||||||
|
form.hook.clearErrors();
|
||||||
|
form.hook.reset();
|
||||||
|
delete form.unload;
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
local.render();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = on_load();
|
||||||
|
const loaded = (values: any) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.dismiss();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
if (!!values) {
|
||||||
|
for (const [k, v] of Object.entries(values)) {
|
||||||
|
form.hook.setValue(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (res instanceof Promise) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isEditor && sonar === "on") {
|
||||||
|
toast.loading(
|
||||||
|
<>
|
||||||
|
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||||
|
Loading data...
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
res.then(loaded);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
loaded(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.submit = submit;
|
||||||
|
|
||||||
|
if (document.getElementsByClassName("prasi-toaster").length === 0) {
|
||||||
|
const elemDiv = document.createElement("div");
|
||||||
|
elemDiv.className = "prasi-toaster";
|
||||||
|
document.body.appendChild(elemDiv);
|
||||||
|
}
|
||||||
|
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
|
||||||
|
|
||||||
|
if (form.unload)
|
||||||
|
return (
|
||||||
|
<div className="c-p-6 c-flex c-flex-col c-space-y-2 c-w-full c-flex-1 c-items-start">
|
||||||
|
<Skeleton className="c-h-3 c-w-[50%]" />
|
||||||
|
<Skeleton className="c-h-3 c-w-[40%]" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormInternal {...form_hook} form={form}>
|
||||||
|
{toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
|
||||||
|
<form
|
||||||
|
className={cx(
|
||||||
|
"flex-1 flex flex-col w-full items-stretch relative overflow-auto",
|
||||||
|
css`
|
||||||
|
.c-text-destructive {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
local.layout === "unknown" && "c-hidden",
|
||||||
|
local.layout === "2-col" &&
|
||||||
|
css`
|
||||||
|
> div {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
> div {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
ref={(el) => {
|
||||||
|
if (el) form.ref = el;
|
||||||
|
|
||||||
|
if (el && layout === "auto" && local.layout === "unknown") {
|
||||||
|
let cur: any = el;
|
||||||
|
let i = 0;
|
||||||
|
while (cur.parentNode && cur.getBoundingClientRect().width === 0) {
|
||||||
|
cur = cur.parentNode;
|
||||||
|
i++;
|
||||||
|
if (i > 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur.getBoundingClientRect().width < 500) {
|
||||||
|
local.layout = "1-col";
|
||||||
|
} else {
|
||||||
|
local.layout = "2-col";
|
||||||
|
}
|
||||||
|
|
||||||
|
local.render(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
submit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PassProp submit={submit} data={form_hook.getValues()}>
|
||||||
|
{body}
|
||||||
|
</PassProp>
|
||||||
|
</form>
|
||||||
|
</FormInternal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormInternal = (props: any) => {
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (props.form && props.form.unload) {
|
||||||
|
props.form.unload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return <FForm {...props} />;
|
||||||
|
};
|
||||||
|
|
@ -1,204 +1,34 @@
|
||||||
import { Form as FForm } from "@/comps/ui/form";
|
|
||||||
import { Toaster } from "@/comps/ui/sonner";
|
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
import { FC } from "react";
|
||||||
import { FC, useEffect } from "react";
|
import { FMInternal, FMProps } from "./typings";
|
||||||
|
import { formReload } from "./utils/reload";
|
||||||
|
import { formInit } from "./utils/init";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { useForm } from "react-hook-form";
|
import { Toaster } from "sonner";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Skeleton } from "../ui/skeleton";
|
|
||||||
import { FormHook } from "./utils/utils";
|
|
||||||
|
|
||||||
export const Form: FC<{
|
export const Form: FC<FMProps> = (props) => {
|
||||||
on_init: (arg: { submit: any; reload: any }) => any;
|
const { PassProp, body } = props;
|
||||||
on_load: () => any;
|
const fm = useLocal<FMInternal>({
|
||||||
on_submit: (arg: { form: any; error: any }) => Promise<any>;
|
data: "",
|
||||||
body: any;
|
status: "init",
|
||||||
form: FormHook;
|
reload: async () => {
|
||||||
PassProp: any;
|
formReload(fm);
|
||||||
cache: () => any;
|
},
|
||||||
sonar: "on" | "off";
|
error: {} as any,
|
||||||
layout: "auto" | "1-col" | "2-col";
|
internal: {
|
||||||
}> = ({
|
reload: {
|
||||||
on_init,
|
timeout: null as any,
|
||||||
on_load,
|
promises: [],
|
||||||
body,
|
done: [],
|
||||||
form,
|
|
||||||
PassProp,
|
|
||||||
on_submit,
|
|
||||||
cache,
|
|
||||||
layout: _layout,
|
|
||||||
sonar,
|
|
||||||
}) => {
|
|
||||||
const form_hook = useForm<any>({
|
|
||||||
defaultValues: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const local = useLocal({
|
|
||||||
el: null as any,
|
|
||||||
submit_timeout: null as any,
|
|
||||||
submit_done: [] as any[],
|
|
||||||
layout: "unknown" as "unknown" | "2-col" | "1-col",
|
|
||||||
init: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
form.hook = form_hook;
|
|
||||||
if (!form.cache && typeof cache === "function") {
|
|
||||||
try {
|
|
||||||
form.cache = cache() || {};
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
if (!form.cache) form.cache = {};
|
|
||||||
|
|
||||||
if (!form.validation) {
|
|
||||||
form.validation = {};
|
|
||||||
}
|
|
||||||
if (!form.label) {
|
|
||||||
form.label = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
let layout = _layout || "auto";
|
|
||||||
if (layout !== "auto") local.layout = layout;
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
return new Promise<boolean>((done) => {
|
|
||||||
local.submit_done.push(done);
|
|
||||||
const done_all = (val: boolean) => {
|
|
||||||
for (const d of local.submit_done) {
|
|
||||||
d(val);
|
|
||||||
}
|
|
||||||
local.submit_done = [];
|
|
||||||
local.render();
|
|
||||||
};
|
|
||||||
|
|
||||||
clearTimeout(local.submit_timeout);
|
|
||||||
local.submit_timeout = setTimeout(async () => {
|
|
||||||
if (sonar === "on") {
|
|
||||||
toast.loading(
|
|
||||||
<>
|
|
||||||
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
|
||||||
Processing ...
|
|
||||||
</>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #e4f7ff;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const data = form.hook.getValues();
|
|
||||||
form.hook.clearErrors();
|
|
||||||
for (const [k, v] of Object.entries(form.validation)) {
|
|
||||||
if (v === "required") {
|
|
||||||
if (!data[k]) {
|
|
||||||
const error = {
|
|
||||||
type: "required",
|
|
||||||
message: `${form.label[k] || k} is required.`,
|
|
||||||
};
|
|
||||||
form.hook.formState.errors[k] = error;
|
|
||||||
form.hook.setError(k, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = on_submit({
|
|
||||||
form: data,
|
|
||||||
error: form.hook.formState.errors,
|
|
||||||
});
|
|
||||||
|
|
||||||
const success = await res;
|
|
||||||
toast.dismiss();
|
|
||||||
done_all(success);
|
|
||||||
if (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(form.hook.formState.errors).length} errors.
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
toast.success(
|
|
||||||
<div className="c-flex c-text-blue-700 c-items-center">
|
|
||||||
<Check className="c-h-4 c-w-4 c-mr-1 " />
|
|
||||||
Done
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
className: css`
|
|
||||||
background: #e4f5ff;
|
|
||||||
border: 2px solid blue;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!local.init) {
|
|
||||||
local.init = true;
|
|
||||||
on_init({
|
|
||||||
submit,
|
|
||||||
reload: () => {
|
|
||||||
local.init = false;
|
|
||||||
form.unload = () => {
|
|
||||||
form.hook.clearErrors();
|
|
||||||
form.hook.reset();
|
|
||||||
delete form.unload;
|
|
||||||
local.render();
|
|
||||||
};
|
|
||||||
local.render();
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
props: {} as any,
|
||||||
|
});
|
||||||
|
|
||||||
const res = on_load();
|
if (fm.status === "init") {
|
||||||
const loaded = (values: any) => {
|
formInit(fm, props);
|
||||||
setTimeout(() => {
|
|
||||||
toast.dismiss();
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
if (!!values) {
|
|
||||||
for (const [k, v] of Object.entries(values)) {
|
|
||||||
form.hook.setValue(k, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
local.render();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (res instanceof Promise) {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!isEditor && sonar === "on") {
|
|
||||||
toast.loading(
|
|
||||||
<>
|
|
||||||
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
|
||||||
Loading data...
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
res.then(loaded);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
loaded(res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form.submit = submit;
|
|
||||||
|
|
||||||
if (document.getElementsByClassName("prasi-toaster").length === 0) {
|
if (document.getElementsByClassName("prasi-toaster").length === 0) {
|
||||||
const elemDiv = document.createElement("div");
|
const elemDiv = document.createElement("div");
|
||||||
elemDiv.className = "prasi-toaster";
|
elemDiv.className = "prasi-toaster";
|
||||||
|
|
@ -206,81 +36,10 @@ export const Form: FC<{
|
||||||
}
|
}
|
||||||
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
|
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
|
||||||
|
|
||||||
if (form.unload)
|
|
||||||
return (
|
|
||||||
<div className="c-p-6 c-flex c-flex-col c-space-y-2 c-w-full c-flex-1 c-items-start">
|
|
||||||
<Skeleton className="c-h-3 c-w-[50%]" />
|
|
||||||
<Skeleton className="c-h-3 c-w-[40%]" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormInternal {...form_hook} form={form}>
|
<>
|
||||||
{toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
|
{toaster_el && createPortal(<Toaster cn={cx} />, toaster_el)}
|
||||||
<form
|
<PassProp>{body}</PassProp>
|
||||||
className={cx(
|
</>
|
||||||
"flex-1 flex flex-col w-full items-stretch relative overflow-auto",
|
|
||||||
css`
|
|
||||||
.c-text-destructive {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
local.layout === "unknown" && "c-hidden",
|
|
||||||
local.layout === "2-col" &&
|
|
||||||
css`
|
|
||||||
> div {
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
> div {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
ref={(el) => {
|
|
||||||
if (el) form.ref = el;
|
|
||||||
|
|
||||||
if (el && layout === "auto" && local.layout === "unknown") {
|
|
||||||
let cur: any = el;
|
|
||||||
let i = 0;
|
|
||||||
while (cur.parentNode && cur.getBoundingClientRect().width === 0) {
|
|
||||||
cur = cur.parentNode;
|
|
||||||
i++;
|
|
||||||
if (i > 10) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur.getBoundingClientRect().width < 500) {
|
|
||||||
local.layout = "1-col";
|
|
||||||
} else {
|
|
||||||
local.layout = "2-col";
|
|
||||||
}
|
|
||||||
|
|
||||||
local.render(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
submit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PassProp submit={submit} data={form_hook.getValues()}>
|
|
||||||
{body}
|
|
||||||
</PassProp>
|
|
||||||
</form>
|
|
||||||
</FormInternal>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormInternal = (props: any) => {
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (props.form && props.form.unload) {
|
|
||||||
props.form.unload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
return <FForm {...props} />;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { FormHook } from "../form-old/utils/utils";
|
||||||
|
|
||||||
|
export type FMProps = {
|
||||||
|
on_init: (arg: { submit: any; reload: any }) => any;
|
||||||
|
on_load: () => any;
|
||||||
|
on_submit: (arg: { form: any; error: any }) => Promise<any>;
|
||||||
|
body: any;
|
||||||
|
form: FormHook;
|
||||||
|
PassProp: any;
|
||||||
|
sonar: "on" | "off";
|
||||||
|
layout: "auto" | "1-col" | "2-col";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FMInternal = {
|
||||||
|
status: "init" | "loading" | "saving" | "ready";
|
||||||
|
data: any;
|
||||||
|
reload: () => Promise<void>;
|
||||||
|
error: {
|
||||||
|
list: { name: string; error: string }[];
|
||||||
|
set: (name: string, error: string) => void;
|
||||||
|
get: (name: string, error: string) => void;
|
||||||
|
clear: () => void;
|
||||||
|
};
|
||||||
|
internal: {
|
||||||
|
reload: {
|
||||||
|
timeout: ReturnType<typeof setTimeout>;
|
||||||
|
promises: Promise<void>[];
|
||||||
|
done: any[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
props: Exclude<FMProps, "body" | "PassProp">;
|
||||||
|
};
|
||||||
|
export type FMLocal = FMInternal & { render: () => void };
|
||||||
|
|
||||||
|
export const FormType = `{
|
||||||
|
status: "init" | "loading" | "saving" | "ready"
|
||||||
|
}`;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { FMLocal } from "../typings";
|
||||||
|
|
||||||
|
export const formError = (fm: FMLocal) => {
|
||||||
|
const error = {} as FMLocal["error"];
|
||||||
|
error.list = [];
|
||||||
|
error.clear = () => {};
|
||||||
|
error.set = () => {};
|
||||||
|
error.get = () => {};
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { FMLocal, FMProps } from "../typings";
|
||||||
|
import { formError } from "./error";
|
||||||
|
|
||||||
|
export const formInit = (fm: FMLocal, props: FMProps) => {
|
||||||
|
for (const [k, v] of Object.entries(props)) {
|
||||||
|
if (["PassProp", "body"].includes(k)) continue;
|
||||||
|
(fm.props as any)[k] = v;
|
||||||
|
}
|
||||||
|
const { on_load, sonar } = fm.props;
|
||||||
|
fm.error = formError(fm);
|
||||||
|
|
||||||
|
fm.reload = () => {
|
||||||
|
const promise = new Promise<void>((done) => {
|
||||||
|
fm.internal.reload.done.push(done);
|
||||||
|
clearTimeout(fm.internal.reload.timeout);
|
||||||
|
fm.internal.reload.timeout = setTimeout(async () => {
|
||||||
|
if (sonar === "on" && !isEditor) {
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.loading(
|
||||||
|
<>
|
||||||
|
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||||
|
Loading data...
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = on_load({ fm });
|
||||||
|
|
||||||
|
fm.internal.reload.done.map((e) => e());
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
fm.internal.reload.promises.push(promise);
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { FMLocal } from "../typings";
|
||||||
|
|
||||||
|
export const formReload = (fm: FMLocal) => {};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import { FC, ReactElement, useEffect } from "react";
|
import { FC, ReactElement, useEffect } from "react";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../../ui/skeleton";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
|
|
||||||
type ListProp = {
|
type ListProp = {
|
||||||
|
|
@ -7,7 +7,7 @@ import DataGrid, {
|
||||||
SortColumn,
|
SortColumn,
|
||||||
} from "react-data-grid";
|
} from "react-data-grid";
|
||||||
import "react-data-grid/lib/styles.css";
|
import "react-data-grid/lib/styles.css";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../../ui/skeleton";
|
||||||
|
|
||||||
type OnRowClick = {
|
type OnRowClick = {
|
||||||
row: any;
|
row: any;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export type TLInternal = {};
|
||||||
|
export type TL = TLInternal & { render: (force?: boolean) => void };
|
||||||
|
export type TableListType = `{}`;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { BreadItem } from "@/comps/custom/Breadcrumb";
|
import { BreadItem } from "@/comps/custom/Breadcrumb";
|
||||||
import { GFCol } from "@/gen/utils";
|
import { GFCol } from "@/gen/utils";
|
||||||
import { ReactElement, ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
export type MDActions = {
|
export type MDActions = {
|
||||||
action?: string;
|
action?: string;
|
||||||
|
|
|
||||||
8
data.ts
8
data.ts
|
|
@ -1,9 +1,11 @@
|
||||||
export { getProp } from "./comps/md/utils/get-prop";
|
export { getProp } from "@/comps/md/utils/get-prop";
|
||||||
export { MasterDetailType } from "./comps/md/utils/typings";
|
export { MasterDetailType } from "@/comps/md/utils/typings";
|
||||||
export { TableList } from "@/comps/list/TableList";
|
export { TableList } from "@/comps/list/TableList";
|
||||||
export { MasterDetail } from "@/comps/md/MasterDetail";
|
export { MasterDetail } from "@/comps/md/MasterDetail";
|
||||||
export { MDAction } from "./comps/md/MDAction";
|
export { MDAction } from "./comps/md/MDAction";
|
||||||
export { Form } from "@/comps/form/Form";
|
export { Form } from "@/comps/form/Form";
|
||||||
export { Field } from "@/comps/form/Field";
|
export { FormType } from "@/comps/form/typings";
|
||||||
|
export { Field } from "@/comps/form-old/Field";
|
||||||
export { prasi_gen } from "@/gen/prasi_gen";
|
export { prasi_gen } from "@/gen/prasi_gen";
|
||||||
export { FormatValue } from "@/utils/format-value";
|
export { FormatValue } from "@/utils/format-value";
|
||||||
|
export { TableListType } from "@/comps/list/typings";
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
export { Card } from "@/comps/custom/Card";
|
export { Card } from "@/comps/custom/Card";
|
||||||
export { Detail } from "@/comps/custom/Detail";
|
export { Detail } from "@/comps/custom/Detail";
|
||||||
export { Tab } from "@/comps/custom/Tab";
|
export { Tab } from "@/comps/custom/Tab";
|
||||||
export { Field } from "@/comps/form/Field";
|
export { Field } from "@/comps/form-old/Field";
|
||||||
export { Form } from "@/comps/form/Form";
|
export { Form } from "@/comps/form/Form";
|
||||||
export { formatMoney } from "@/comps/form/InputMoney";
|
export { formatMoney } from "@/comps/form-old/InputMoney";
|
||||||
export { icon } from "@/comps/icon";
|
export { icon } from "@/comps/icon";
|
||||||
export { List } from "@/comps/list/List";
|
export { List } from "@/comps/list/old/List";
|
||||||
export { Slider } from "@/comps/ui/slider";
|
export { Slider } from "@/comps/ui/slider";
|
||||||
export * from "@/utils/date";
|
export * from "@/utils/date";
|
||||||
export { Button, FloatButton } from "@/comps/ui/button";
|
export { Button, FloatButton } from "@/comps/ui/button";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue