fixing lib
This commit is contained in:
parent
ab03212230
commit
2e6d2b5388
|
|
@ -28,20 +28,9 @@ export const Form: FC<FMProps> = (props) => {
|
||||||
events: {
|
events: {
|
||||||
on_change(name: string, new_value: any) {},
|
on_change(name: string, new_value: any) {},
|
||||||
},
|
},
|
||||||
|
internal: {},
|
||||||
submit: null as any,
|
submit: null as any,
|
||||||
error: {} as any,
|
error: {} as any,
|
||||||
internal: {
|
|
||||||
reload: {
|
|
||||||
timeout: null as any,
|
|
||||||
promises: [],
|
|
||||||
done: [],
|
|
||||||
},
|
|
||||||
submit: {
|
|
||||||
timeout: null as any,
|
|
||||||
promises: [],
|
|
||||||
done: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
field_def: {},
|
field_def: {},
|
||||||
props: {} as any,
|
props: {} as any,
|
||||||
size: {
|
size: {
|
||||||
|
|
@ -164,6 +153,7 @@ export const Form: FC<FMProps> = (props) => {
|
||||||
formInit(fm, props);
|
formInit(fm, props);
|
||||||
fm.reload();
|
fm.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
||||||
|
|
|
||||||
|
|
@ -149,13 +149,13 @@ ${
|
||||||
fm.status = "ready";
|
fm.status = "ready";
|
||||||
fm.data = form;
|
fm.data = form;
|
||||||
md.selected = form;
|
md.selected = form;
|
||||||
|
if (md.props.mode !== "full") md.master.reload({ toast: false });
|
||||||
md.render();
|
md.render();
|
||||||
fm.render();
|
fm.render();
|
||||||
|
|
||||||
if (fm.props.back_on_save === "y") {
|
if (fm.props.back_on_save === "y") {
|
||||||
md.selected = null;
|
md.selected = null;
|
||||||
md.tab.active = "master";
|
md.tab.active = "master";
|
||||||
md.internal.action_should_refresh = true;
|
|
||||||
md.params.apply();
|
md.params.apply();
|
||||||
md.render();
|
md.render();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,16 +139,6 @@ export type FMInternal = {
|
||||||
clear: (name?: string) => void;
|
clear: (name?: string) => void;
|
||||||
};
|
};
|
||||||
internal: {
|
internal: {
|
||||||
reload: {
|
|
||||||
timeout: ReturnType<typeof setTimeout>;
|
|
||||||
promises: Promise<void>[];
|
|
||||||
done: any[];
|
|
||||||
};
|
|
||||||
submit: {
|
|
||||||
promises: Promise<boolean>[];
|
|
||||||
timeout: ReturnType<typeof setTimeout>;
|
|
||||||
done: any[];
|
|
||||||
};
|
|
||||||
original_render?: () => void;
|
original_render?: () => void;
|
||||||
};
|
};
|
||||||
props: Exclude<FMProps, "body" | "PassProp">;
|
props: Exclude<FMProps, "body" | "PassProp">;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { parseGenField } from "@/gen/utils";
|
import { parseGenField } from "@/gen/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { FMLocal, FMProps } from "../typings";
|
import { FMLocal, FMProps } from "../typings";
|
||||||
import { editorFormData } from "./ed-data";
|
import { editorFormData } from "./ed-data";
|
||||||
import { formError } from "./error";
|
import { formError } from "./error";
|
||||||
|
import { toast } from "lib/comps/ui/toast";
|
||||||
|
|
||||||
export const formInit = (fm: FMLocal, props: FMProps) => {
|
export const formInit = (fm: FMLocal, props: FMProps) => {
|
||||||
for (const [k, v] of Object.entries(props)) {
|
for (const [k, v] of Object.entries(props)) {
|
||||||
|
|
@ -19,77 +19,165 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
|
||||||
fm.field_def[d.name] = d;
|
fm.field_def[d.name] = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
fm.reload = () => {
|
fm.reload = async () => {
|
||||||
fm.status = isEditor ? "ready" : "loading";
|
fm.status = isEditor ? "ready" : "loading";
|
||||||
fm.render();
|
fm.render();
|
||||||
|
|
||||||
const promise = new Promise<void>((done) => {
|
if (sonar === "on" && !isEditor) {
|
||||||
fm.internal.reload.done.push(done);
|
toast.dismiss();
|
||||||
clearTimeout(fm.internal.reload.timeout);
|
toast.loading(
|
||||||
fm.internal.reload.timeout = setTimeout(async () => {
|
<>
|
||||||
if (sonar === "on" && !isEditor) {
|
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||||
setTimeout(() => {
|
Loading data...
|
||||||
toast.dismiss();
|
</>
|
||||||
toast.loading(
|
);
|
||||||
<>
|
}
|
||||||
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
|
||||||
Loading data...
|
let should_load = true;
|
||||||
</>
|
if (isEditor) {
|
||||||
);
|
const item_id = props.item.id;
|
||||||
});
|
if (item_id) {
|
||||||
|
const cache = editorFormData[item_id];
|
||||||
|
if (
|
||||||
|
cache &&
|
||||||
|
cache.on_load === get(props.item, "component.props.on_load.value")
|
||||||
|
) {
|
||||||
|
fm.data = cache.data;
|
||||||
|
should_load = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (should_load) {
|
||||||
|
if (!on_load) {
|
||||||
|
console.error("Form on_load is empty. Please re-generate the form.");
|
||||||
|
} else {
|
||||||
|
const on_load_result = on_load({ fm });
|
||||||
|
let result = undefined;
|
||||||
|
if (
|
||||||
|
typeof on_load_result === "object" &&
|
||||||
|
on_load_result instanceof Promise
|
||||||
|
) {
|
||||||
|
result = await on_load_result;
|
||||||
|
} else {
|
||||||
|
result = on_load_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_load = true;
|
fm.data = result;
|
||||||
|
|
||||||
|
if (result === undefined) fm.data = {};
|
||||||
|
|
||||||
if (isEditor) {
|
if (isEditor) {
|
||||||
const item_id = props.item.id;
|
const item_id = props.item.id;
|
||||||
if (item_id) {
|
if (item_id) {
|
||||||
const cache = editorFormData[item_id];
|
editorFormData[item_id] = {
|
||||||
if (
|
data: fm.data,
|
||||||
cache &&
|
on_load: get(props.item, "component.props.on_load.value"),
|
||||||
cache.on_load === get(props.item, "component.props.on_load.value")
|
};
|
||||||
) {
|
|
||||||
fm.data = cache.data;
|
|
||||||
should_load = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (should_load) {
|
}
|
||||||
if (!on_load) {
|
}
|
||||||
console.error("Form on_load is empty. Please re-generate the form.");
|
|
||||||
} else {
|
toast.dismiss();
|
||||||
const on_load_result = on_load({ fm });
|
|
||||||
let result = undefined;
|
if (fm.is_newly_created) {
|
||||||
if (
|
fm.is_newly_created = false;
|
||||||
typeof on_load_result === "object" &&
|
toast.success(
|
||||||
on_load_result instanceof Promise
|
<div className="c-flex c-text-green-700 c-items-center">
|
||||||
) {
|
<Check className="c-h-4 c-w-4 c-mr-1 " />
|
||||||
result = await on_load_result;
|
Saved
|
||||||
} else {
|
</div>,
|
||||||
result = on_load_result;
|
{
|
||||||
|
className: css`
|
||||||
|
background: #e4ffed;
|
||||||
|
border: 2px solid green;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fm.status = "ready";
|
||||||
|
fm.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
fm.submit = async () => {
|
||||||
|
if (fm.status !== "ready") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof fm.props.on_submit === "function") {
|
||||||
|
fm.status = "saving";
|
||||||
|
fm.render();
|
||||||
|
|
||||||
|
if (fm.props.sonar === "on" && !isEditor) {
|
||||||
|
toast.loading(
|
||||||
|
<>
|
||||||
|
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||||
|
Submitting...
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
fm.data = result;
|
if (typeof obj === "object" && obj) {
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
if (result === undefined) fm.data = {};
|
form[k] = v;
|
||||||
|
|
||||||
if (isEditor) {
|
|
||||||
const item_id = props.item.id;
|
|
||||||
if (item_id) {
|
|
||||||
editorFormData[item_id] = {
|
|
||||||
data: fm.data,
|
|
||||||
on_load: get(props.item, "component.props.on_load.value"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fm.internal.reload.done.map((e) => e());
|
const success = await fm.props.on_submit({
|
||||||
setTimeout(() => {
|
fm,
|
||||||
toast.dismiss();
|
form,
|
||||||
|
error: fm.error.object,
|
||||||
|
});
|
||||||
|
|
||||||
if (fm.is_newly_created) {
|
toast.dismiss();
|
||||||
fm.is_newly_created = false;
|
|
||||||
|
if (!success) {
|
||||||
|
fm.status = "ready";
|
||||||
|
fm.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fm.props.sonar === "on" && !isEditor) {
|
||||||
|
toast.dismiss();
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
const errors = Object.keys(fm.error.list);
|
||||||
|
const count = errors.length;
|
||||||
|
console.log(fm.error.list);
|
||||||
|
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
|
||||||
|
{count > 0 &&
|
||||||
|
`, please correct
|
||||||
|
${count} errors`}
|
||||||
|
.
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
dismissible: true,
|
||||||
|
className: css`
|
||||||
|
background: #ffecec;
|
||||||
|
border: 2px solid red;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (!fm.is_newly_created) {
|
||||||
toast.success(
|
toast.success(
|
||||||
<div className="c-flex c-text-green-700 c-items-center">
|
<div className="c-flex c-text-green-700 c-items-center">
|
||||||
<Check className="c-h-4 c-w-4 c-mr-1 " />
|
<Check className="c-h-4 c-w-4 c-mr-1 " />
|
||||||
|
|
@ -103,127 +191,10 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, 100);
|
|
||||||
|
|
||||||
fm.status = "ready";
|
|
||||||
fm.render();
|
|
||||||
}, 50);
|
|
||||||
});
|
|
||||||
fm.internal.reload.promises.push(promise);
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
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") {
|
|
||||||
fm.status = "saving";
|
|
||||||
fm.render();
|
|
||||||
|
|
||||||
if (fm.props.sonar === "on" && !isEditor) {
|
|
||||||
toast.loading(
|
|
||||||
<>
|
|
||||||
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
|
||||||
Submitting...
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
error: fm.error.object,
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.dismiss();
|
|
||||||
done_all(success);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
fm.status = "ready";
|
|
||||||
fm.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fm.props.sonar === "on" && !isEditor) {
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.dismiss();
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
const errors = Object.keys(fm.error.list);
|
|
||||||
const count = errors.length;
|
|
||||||
console.log(fm.error.list);
|
|
||||||
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
|
|
||||||
{count > 0 &&
|
|
||||||
`, please correct
|
|
||||||
${count} errors`}
|
|
||||||
.
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!fm.is_newly_created) {
|
|
||||||
toast.success(
|
|
||||||
<div className="c-flex c-text-green-700 c-items-center">
|
|
||||||
<Check className="c-h-4 c-w-4 c-mr-1 " />
|
|
||||||
Saved
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
className: css`
|
|
||||||
background: #e4ffed;
|
|
||||||
border: 2px solid green;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 100);
|
}
|
||||||
});
|
return success;
|
||||||
fm.internal.submit.promises.push(promise);
|
}
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
};
|
||||||
if (typeof fm.props.on_init === "function") {
|
if (typeof fm.props.on_init === "function") {
|
||||||
fm.props.on_init({ fm, submit: fm.submit, reload: fm.reload });
|
fm.props.on_init({ fm, submit: fm.submit, reload: fm.reload });
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,14 @@ import DataGrid, {
|
||||||
} from "react-data-grid";
|
} from "react-data-grid";
|
||||||
import "react-data-grid/lib/styles.css";
|
import "react-data-grid/lib/styles.css";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { Toaster, toast } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import { call_prasi_events } from "../../..";
|
import { call_prasi_events } from "../../..";
|
||||||
import { filterWhere } from "../filter/parser/filter-where";
|
import { filterWhere } from "../filter/parser/filter-where";
|
||||||
import { getFilter } from "../filter/utils/get-filter";
|
import { getFilter } from "../filter/utils/get-filter";
|
||||||
import { MDLocal } from "../md/utils/typings";
|
import { MDLocal } from "../md/utils/typings";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../ui/skeleton";
|
||||||
import { sortTree } from "./utils/sort-tree";
|
import { sortTree } from "./utils/sort-tree";
|
||||||
|
import { toast } from "../ui/toast";
|
||||||
|
|
||||||
type OnRowClick = (arg: {
|
type OnRowClick = (arg: {
|
||||||
row: any;
|
row: any;
|
||||||
|
|
@ -37,6 +38,8 @@ type OnRowClick = (arg: {
|
||||||
idx: any;
|
idx: any;
|
||||||
event: React.MouseEvent<HTMLDivElement>;
|
event: React.MouseEvent<HTMLDivElement>;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
let EMPTY_SET = new Set() as ReadonlySet<any>;
|
||||||
|
|
||||||
type SelectedRow = (arg: { row: any; rows: any[]; idx: any }) => boolean;
|
type SelectedRow = (arg: { row: any; rows: any[]; idx: any }) => boolean;
|
||||||
type TableListProp = {
|
type TableListProp = {
|
||||||
child: any;
|
child: any;
|
||||||
|
|
@ -147,6 +150,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
| "init"
|
| "init"
|
||||||
| "error",
|
| "error",
|
||||||
where: null as any,
|
where: null as any,
|
||||||
|
should_toast: true,
|
||||||
paging: {
|
paging: {
|
||||||
take: 0,
|
take: 0,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
|
|
@ -235,75 +239,82 @@ export const TableList: FC<TableListProp> = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const reload = useCallback(() => {
|
const reload = useCallback(
|
||||||
if (typeof on_load === "function") {
|
(arg?: { toast: boolean }) => {
|
||||||
local.status = "loading";
|
let should_toast = true;
|
||||||
local.render();
|
if (arg?.toast === false) should_toast = false;
|
||||||
|
local.should_toast = should_toast;
|
||||||
|
|
||||||
const orderBy = local.sort.orderBy || undefined;
|
if (typeof on_load === "function") {
|
||||||
const where = filterWhere(filter_name, __props);
|
local.status = "loading";
|
||||||
|
|
||||||
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 = {
|
|
||||||
async reload() {},
|
|
||||||
orderBy,
|
|
||||||
where,
|
|
||||||
paging: {
|
|
||||||
take: local.paging.take > 0 ? local.paging.take : undefined,
|
|
||||||
skip: local.paging.skip,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (id_parent) {
|
|
||||||
load_args.paging = {};
|
|
||||||
}
|
|
||||||
const result = on_load({ ...load_args, mode: "query" });
|
|
||||||
const callback = (data: any[]) => {
|
|
||||||
if (local.paging.skip === 0) {
|
|
||||||
local.data = data;
|
|
||||||
} else {
|
|
||||||
local.data = [...local.data, ...data];
|
|
||||||
}
|
|
||||||
|
|
||||||
local.status = "ready";
|
|
||||||
local.render();
|
local.render();
|
||||||
};
|
|
||||||
|
|
||||||
if (result instanceof Promise) {
|
const orderBy = local.sort.orderBy || undefined;
|
||||||
(async () => {
|
const where = filterWhere(filter_name, __props);
|
||||||
try {
|
|
||||||
callback(await result);
|
if (md) {
|
||||||
} catch (e) {
|
const last = md.params.links[md.params.links.length - 1];
|
||||||
console.error(e);
|
if (last && last.where) {
|
||||||
local.status = "error";
|
for (const [k, v] of Object.entries(last.where)) {
|
||||||
toast.dismiss();
|
where[k] = v;
|
||||||
toast.error(
|
}
|
||||||
<div className="c-flex c-text-red-600 c-items-center">
|
|
||||||
<AlertTriangle className="c-h-4 c-w-4 c-mr-1" />
|
|
||||||
Failed to load data
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
} else callback(result);
|
|
||||||
}
|
call_prasi_events("tablelist", "where", [__props?.gen__table, where]);
|
||||||
}, [on_load, local.sort.orderBy, local.paging.take, local.paging.skip]);
|
|
||||||
|
const load_args: any = {
|
||||||
|
async reload() {},
|
||||||
|
orderBy,
|
||||||
|
where,
|
||||||
|
paging: {
|
||||||
|
take: local.paging.take > 0 ? local.paging.take : undefined,
|
||||||
|
skip: local.paging.skip,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (id_parent) {
|
||||||
|
load_args.paging = {};
|
||||||
|
}
|
||||||
|
const result = on_load({ ...load_args, mode: "query" });
|
||||||
|
const callback = (data: any[]) => {
|
||||||
|
if (local.paging.skip === 0) {
|
||||||
|
local.data = data;
|
||||||
|
} else {
|
||||||
|
local.data = [...local.data, ...data];
|
||||||
|
}
|
||||||
|
|
||||||
|
local.status = "ready";
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
callback(await result);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
local.status = "error";
|
||||||
|
toast.dismiss();
|
||||||
|
toast.error(
|
||||||
|
<div className="c-flex c-text-red-600 c-items-center">
|
||||||
|
<AlertTriangle className="c-h-4 c-w-4 c-mr-1" />
|
||||||
|
Failed to load data
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
dismissible: true,
|
||||||
|
className: css`
|
||||||
|
background: #ffecec;
|
||||||
|
border: 2px solid red;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
} else callback(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[on_load, local.sort.orderBy, local.paging.take, local.paging.skip]
|
||||||
|
);
|
||||||
|
|
||||||
if (md) {
|
if (md) {
|
||||||
md.master.reload = reload;
|
md.master.reload = reload;
|
||||||
|
|
@ -540,16 +551,20 @@ export const TableList: FC<TableListProp> = ({
|
||||||
|
|
||||||
if (!isEditor) {
|
if (!isEditor) {
|
||||||
if (local.status === "loading") {
|
if (local.status === "loading") {
|
||||||
toast.dismiss();
|
if (local.should_toast) {
|
||||||
toast.loading(
|
toast.dismiss();
|
||||||
<>
|
toast.loading(
|
||||||
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
<>
|
||||||
Loading {local.paging.skip === 0 ? "Data" : "more rows"} ...
|
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||||
</>,
|
Loading {local.paging.skip === 0 ? "Data" : "more rows"} ...
|
||||||
{
|
</>,
|
||||||
dismissible: true,
|
{
|
||||||
}
|
dismissible: true,
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
local.should_toast = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (local.status !== "error") {
|
if (local.status !== "error") {
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
|
|
@ -682,7 +697,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
rows={data}
|
rows={data}
|
||||||
className="rdg-light"
|
className="rdg-light"
|
||||||
onScroll={local.paging.scroll}
|
onScroll={local.paging.scroll}
|
||||||
selectedRows={new Set() as ReadonlySet<any>}
|
selectedRows={EMPTY_SET}
|
||||||
onSelectedCellChange={() => {}}
|
onSelectedCellChange={() => {}}
|
||||||
onSelectedRowsChange={() => {}}
|
onSelectedRowsChange={() => {}}
|
||||||
headerRowHeight={show_header === false ? 0 : undefined}
|
headerRowHeight={show_header === false ? 0 : undefined}
|
||||||
|
|
@ -722,7 +737,10 @@ export const TableList: FC<TableListProp> = ({
|
||||||
isRowSelected={true}
|
isRowSelected={true}
|
||||||
className={cx(
|
className={cx(
|
||||||
props.className,
|
props.className,
|
||||||
isSelect && "row-selected"
|
(isSelect ||
|
||||||
|
md?.selected?.[local.pk?.name || ""] ===
|
||||||
|
props.row[local.pk?.name || ""]) &&
|
||||||
|
"row-selected"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -911,7 +929,7 @@ const dataGridStyle = (local: { height: number }) => css`
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-selected {
|
.row-selected {
|
||||||
background: #e2f1ff;
|
background: #bddfff !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ export const MasterDetail: FC<MDProps> = (arg) => {
|
||||||
on_init,
|
on_init,
|
||||||
_item,
|
_item,
|
||||||
title,
|
title,
|
||||||
|
detail_size,
|
||||||
} = arg;
|
} = arg;
|
||||||
const _ref = useRef({ PassProp, item: _item, childs: {} });
|
const _ref = useRef({ PassProp, item: _item, childs: {} });
|
||||||
const mdr = _ref.current;
|
const mdr = _ref.current;
|
||||||
|
|
@ -47,7 +48,7 @@ export const MasterDetail: FC<MDProps> = (arg) => {
|
||||||
active: "",
|
active: "",
|
||||||
list: [],
|
list: [],
|
||||||
},
|
},
|
||||||
internal: { action_should_refresh: true },
|
internal: { action_should_refresh: false },
|
||||||
childs: {},
|
childs: {},
|
||||||
props: {
|
props: {
|
||||||
mode,
|
mode,
|
||||||
|
|
@ -70,11 +71,8 @@ export const MasterDetail: FC<MDProps> = (arg) => {
|
||||||
masterDetailApplyParams(md);
|
masterDetailApplyParams(md);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
detail_size: Number(detail_size || "400"),
|
||||||
master: { render() {}, reload() {} },
|
master: { render() {}, reload() {} },
|
||||||
panel: {
|
|
||||||
size: 25,
|
|
||||||
min_size: 0,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mdr.PassProp = PassProp;
|
mdr.PassProp = PassProp;
|
||||||
|
|
|
||||||
|
|
@ -86,10 +86,8 @@ export const generateMDForm = async (
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
md.selected = null;
|
md.selected = null;
|
||||||
md.tab.active = "master";
|
md.tab.active = "master";
|
||||||
md.internal.action_should_refresh = true;
|
|
||||||
md.params.apply();
|
md.params.apply();
|
||||||
md.render();
|
md.render();
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ export const generateMDList = async (
|
||||||
value: `\
|
value: `\
|
||||||
({ row, rows, idx, event }: OnRowClick) => {
|
({ row, rows, idx, event }: OnRowClick) => {
|
||||||
md.selected = row;
|
md.selected = row;
|
||||||
md.internal.action_should_refresh = true;
|
|
||||||
md.tab.active = "detail";
|
md.tab.active = "detail";
|
||||||
md.params.apply();
|
md.params.apply();
|
||||||
md.render();
|
md.render();
|
||||||
|
|
|
||||||
|
|
@ -3,35 +3,40 @@ import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
||||||
import { MDMaster } from "../parts/MDMaster";
|
import { MDMaster } from "../parts/MDMaster";
|
||||||
import { MDDetail } from "../parts/MDDetail";
|
import { MDDetail } from "../parts/MDDetail";
|
||||||
import { MDLocal, MDRef } from "../utils/typings";
|
import { MDLocal, MDRef } from "../utils/typings";
|
||||||
|
import { getPathname } from "@/utils/pathname";
|
||||||
|
|
||||||
export const ModeHSplit: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
export const ModeHSplit: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
return (
|
return (
|
||||||
<div className={cx("c-flex-1")}>
|
<div className={cx("c-flex-1")}>
|
||||||
<PanelGroup direction="horizontal">
|
<PanelGroup direction="horizontal">
|
||||||
<Panel
|
<Panel className="c-border-r c-flex">
|
||||||
className="c-border-r c-flex"
|
|
||||||
defaultSize={md.panel.size}
|
|
||||||
minSize={md.panel.min_size}
|
|
||||||
>
|
|
||||||
<MDMaster md={md} mdr={mdr} />
|
<MDMaster md={md} mdr={mdr} />
|
||||||
</Panel>
|
</Panel>
|
||||||
<>
|
{(md.selected || isEditor) && (
|
||||||
<PanelResizeHandle />
|
<>
|
||||||
<Panel
|
<PanelResizeHandle />
|
||||||
className="c-flex c-flex-col c-items-stretch c-w-10"
|
<Panel
|
||||||
defaultSize={
|
className="c-flex c-flex-col c-items-stretch c-w-10"
|
||||||
parseInt(localStorage.getItem(`prasi-md-h-${md.name}`) || "") ||
|
defaultSize={
|
||||||
undefined
|
Number(
|
||||||
}
|
localStorage.getItem(
|
||||||
onResize={(e) => {
|
`prasi-md-${getPathname({ hash: false })}${md.name}`
|
||||||
if (e < 80) {
|
)
|
||||||
localStorage.setItem(`prasi-md-h-${md.name}`, e.toString());
|
) || md.detail_size
|
||||||
}
|
}
|
||||||
}}
|
onResize={(e) => {
|
||||||
>
|
if (e < 80 && !isEditor) {
|
||||||
<MDDetail md={md} mdr={mdr} />
|
localStorage.setItem(
|
||||||
</Panel>
|
`prasi-md-${getPathname({ hash: false })}${md.name}`,
|
||||||
</>
|
e.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MDDetail md={md} mdr={mdr} />
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</PanelGroup>
|
</PanelGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,42 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
||||||
|
import { MDDetail } from "../parts/MDDetail";
|
||||||
import { MDMaster } from "../parts/MDMaster";
|
import { MDMaster } from "../parts/MDMaster";
|
||||||
import { MDLocal, MDRef } from "../utils/typings";
|
import { MDLocal, MDRef } from "../utils/typings";
|
||||||
import { MDDetail, should_show_tab } from "../parts/MDDetail";
|
import { getPathname } from "@/utils/pathname";
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
|
||||||
|
|
||||||
export const ModeVSplit: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
export const ModeVSplit: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
return (
|
return (
|
||||||
<div className={cx("c-flex-1")}>
|
<div className={cx("c-flex-1")}>
|
||||||
<PanelGroup direction="vertical">
|
<PanelGroup direction="vertical">
|
||||||
<Panel
|
<Panel className="c-border-b c-flex">
|
||||||
className="c-border-b"
|
|
||||||
defaultSize={md.panel.size}
|
|
||||||
minSize={md.panel.min_size}
|
|
||||||
>
|
|
||||||
<MDMaster md={md} mdr={mdr} />
|
<MDMaster md={md} mdr={mdr} />
|
||||||
</Panel>
|
</Panel>
|
||||||
<>
|
{(md.selected || isEditor) && (
|
||||||
<PanelResizeHandle />
|
<>
|
||||||
<Panel
|
<PanelResizeHandle />
|
||||||
className="c-flex c-flex-col c-items-stretch"
|
<Panel
|
||||||
defaultSize={
|
className="c-flex c-flex-col c-items-stretch"
|
||||||
parseInt(localStorage.getItem(`prasi-md-h-${md.name}`) || "") ||
|
defaultSize={
|
||||||
undefined
|
Number(
|
||||||
}
|
localStorage.getItem(
|
||||||
onResize={(e) => {
|
`prasi-md-${getPathname({ hash: false })}${md.name}`
|
||||||
if (e < 80) {
|
)
|
||||||
localStorage.setItem(`prasi-md-h-${md.name}`, e.toString());
|
) || md.detail_size
|
||||||
}
|
}
|
||||||
}}
|
onResize={(e) => {
|
||||||
>
|
if (e < 80 && !isEditor) {
|
||||||
<MDDetail md={md} mdr={mdr} />
|
localStorage.setItem(
|
||||||
</Panel>
|
`prasi-md-${getPathname({ hash: false })}${md.name}`,
|
||||||
</>
|
e.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MDDetail md={md} mdr={mdr} />
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</PanelGroup>
|
</PanelGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { FC, useEffect } from "react";
|
||||||
import { breadcrumbPrefix } from "../utils/md-hash";
|
import { breadcrumbPrefix } from "../utils/md-hash";
|
||||||
import { MDLocal, MDRef } from "../utils/typings";
|
import { MDLocal, MDRef } from "../utils/typings";
|
||||||
import { MDHeader } from "./MDHeader";
|
import { MDHeader } from "./MDHeader";
|
||||||
|
import { hashSum } from "lib/utils/hash-sum";
|
||||||
|
|
||||||
export const should_show_tab = (md: MDLocal) => {
|
export const should_show_tab = (md: MDLocal) => {
|
||||||
if (isEditor) {
|
if (isEditor) {
|
||||||
|
|
@ -12,6 +13,7 @@ export const should_show_tab = (md: MDLocal) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MDDetail: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
export const MDDetail: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
|
const local = useLocal({ selected: "", synced: false });
|
||||||
const detail = md.childs[md.tab.active];
|
const detail = md.childs[md.tab.active];
|
||||||
const PassProp = mdr.PassProp;
|
const PassProp = mdr.PassProp;
|
||||||
if (!detail) {
|
if (!detail) {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export type MDProps = {
|
||||||
gen_fields: any;
|
gen_fields: any;
|
||||||
footer: any;
|
footer: any;
|
||||||
gen_table: string;
|
gen_table: string;
|
||||||
|
detail_size: string;
|
||||||
on_init: (md: MDLocal) => void;
|
on_init: (md: MDLocal) => void;
|
||||||
_item: PrasiItem;
|
_item: PrasiItem;
|
||||||
deps?: any[];
|
deps?: any[];
|
||||||
|
|
@ -54,7 +55,11 @@ export type MDLocalInternal = {
|
||||||
list: string[];
|
list: string[];
|
||||||
};
|
};
|
||||||
internal: { action_should_refresh: boolean };
|
internal: { action_should_refresh: boolean };
|
||||||
master: { reload: () => void; render: () => void };
|
master: {
|
||||||
|
reload: (arg?: { toast: boolean }) => void;
|
||||||
|
render: () => void;
|
||||||
|
pk?: string;
|
||||||
|
};
|
||||||
params: {
|
params: {
|
||||||
links: LinkParam[];
|
links: LinkParam[];
|
||||||
hash: Record<string, any>;
|
hash: Record<string, any>;
|
||||||
|
|
@ -74,6 +79,7 @@ export type MDLocalInternal = {
|
||||||
item: PrasiItem;
|
item: PrasiItem;
|
||||||
};
|
};
|
||||||
deps?: object;
|
deps?: object;
|
||||||
|
detail_size: number;
|
||||||
childs: Record<
|
childs: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
|
|
@ -87,10 +93,6 @@ export type MDLocalInternal = {
|
||||||
list?: any;
|
list?: any;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
panel: {
|
|
||||||
size: number;
|
|
||||||
min_size: number;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
export type MDRef = {
|
export type MDRef = {
|
||||||
PassProp: any;
|
PassProp: any;
|
||||||
|
|
@ -133,7 +135,7 @@ export const MasterDetailType = `const md = {
|
||||||
apply: () => void;
|
apply: () => void;
|
||||||
};
|
};
|
||||||
props: {
|
props: {
|
||||||
mode: "full" | "h-split" | "v-split";
|
mode: string;
|
||||||
show_head: "always" | "only-master" | "only-child" | "hidden";
|
show_head: "always" | "only-master" | "only-child" | "hidden";
|
||||||
tab_mode: "h-tab" | "v-tab" | "hidden";
|
tab_mode: "h-tab" | "v-tab" | "hidden";
|
||||||
editor_tab: string;
|
editor_tab: string;
|
||||||
|
|
@ -143,7 +145,7 @@ export const MasterDetailType = `const md = {
|
||||||
};
|
};
|
||||||
internal: { action_should_refresh: boolean };
|
internal: { action_should_refresh: boolean };
|
||||||
render: () => void;
|
render: () => void;
|
||||||
master: { reload: () => void; render: () => void };
|
master: { reload: (arg?:{toast: boolean}) => void; render: () => void };
|
||||||
pk?: {
|
pk?: {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -168,9 +170,6 @@ export const MasterDetailType = `const md = {
|
||||||
md?: md;
|
md?: md;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
panel: {
|
detail_size: number;
|
||||||
size: number;
|
|
||||||
min_size: number;
|
|
||||||
};
|
|
||||||
deps: any
|
deps: any
|
||||||
};`;
|
};`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
import { toast as sonner } from "sonner";
|
||||||
|
const timer = {
|
||||||
|
timeout: null as any,
|
||||||
|
done: false,
|
||||||
|
limit: 400,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toast = {
|
||||||
|
dismiss: () => {
|
||||||
|
if (!timer.timeout) {
|
||||||
|
sonner.dismiss();
|
||||||
|
} else {
|
||||||
|
clearTimeout(timer.timeout);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loading: (
|
||||||
|
el: ReactElement,
|
||||||
|
props?: { dismissible?: boolean; className?: string }
|
||||||
|
) => {
|
||||||
|
clearTimeout(timer.timeout);
|
||||||
|
timer.timeout = setTimeout(() => {
|
||||||
|
sonner.loading(el, props);
|
||||||
|
timer.timeout = null;
|
||||||
|
}, timer.limit);
|
||||||
|
},
|
||||||
|
success: (
|
||||||
|
el: ReactElement,
|
||||||
|
props?: { dismissible?: boolean; className?: string }
|
||||||
|
) => {
|
||||||
|
clearTimeout(timer.timeout);
|
||||||
|
timer.timeout = setTimeout(() => {
|
||||||
|
sonner.success(el, props);
|
||||||
|
timer.timeout = null;
|
||||||
|
}, timer.limit);
|
||||||
|
},
|
||||||
|
error: (
|
||||||
|
el: ReactElement,
|
||||||
|
props?: { dismissible?: boolean; className?: string }
|
||||||
|
) => {
|
||||||
|
clearTimeout(timer.timeout);
|
||||||
|
timer.timeout = setTimeout(() => {
|
||||||
|
sonner.error(el, props);
|
||||||
|
timer.timeout = null;
|
||||||
|
}, timer.limit);
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue