fixing lib

This commit is contained in:
rizky 2024-08-07 07:04:29 -07:00
parent ab03212230
commit 2e6d2b5388
13 changed files with 361 additions and 339 deletions

View File

@ -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";

View File

@ -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();
} }

View File

@ -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">;

View File

@ -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,16 +19,11 @@ 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) => {
fm.internal.reload.done.push(done);
clearTimeout(fm.internal.reload.timeout);
fm.internal.reload.timeout = setTimeout(async () => {
if (sonar === "on" && !isEditor) { if (sonar === "on" && !isEditor) {
setTimeout(() => {
toast.dismiss(); toast.dismiss();
toast.loading( toast.loading(
<> <>
@ -36,7 +31,6 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
Loading data... Loading data...
</> </>
); );
});
} }
let should_load = true; let should_load = true;
@ -84,8 +78,6 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
} }
} }
fm.internal.reload.done.map((e) => e());
setTimeout(() => {
toast.dismiss(); toast.dismiss();
if (fm.is_newly_created) { if (fm.is_newly_created) {
@ -103,29 +95,15 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
} }
); );
} }
}, 100);
fm.status = "ready"; fm.status = "ready";
fm.render(); fm.render();
}, 50);
});
fm.internal.reload.promises.push(promise);
return promise;
}; };
fm.submit = () => { fm.submit = async () => {
const promise = new Promise<boolean>(async (done) => { if (fm.status !== "ready") {
fm.internal.submit.done.push(done); return;
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 (typeof fm.props.on_submit === "function") {
fm.status = "saving"; fm.status = "saving";
fm.render(); fm.render();
@ -168,7 +146,6 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
}); });
toast.dismiss(); toast.dismiss();
done_all(success);
if (!success) { if (!success) {
fm.status = "ready"; fm.status = "ready";
@ -176,7 +153,6 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
} }
if (fm.props.sonar === "on" && !isEditor) { if (fm.props.sonar === "on" && !isEditor) {
setTimeout(() => {
toast.dismiss(); toast.dismiss();
if (!success) { if (!success) {
@ -216,14 +192,9 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
); );
} }
} }
}, 100);
} }
return success;
} }
}, 100);
});
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 });

View File

@ -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,7 +239,12 @@ export const TableList: FC<TableListProp> = ({
}, },
}); });
const reload = useCallback(() => { const reload = useCallback(
(arg?: { toast: boolean }) => {
let should_toast = true;
if (arg?.toast === false) should_toast = false;
local.should_toast = should_toast;
if (typeof on_load === "function") { if (typeof on_load === "function") {
local.status = "loading"; local.status = "loading";
local.render(); local.render();
@ -303,7 +312,9 @@ export const TableList: FC<TableListProp> = ({
})(); })();
} else callback(result); } else callback(result);
} }
}, [on_load, local.sort.orderBy, local.paging.take, local.paging.skip]); },
[on_load, local.sort.orderBy, local.paging.take, local.paging.skip]
);
if (md) { if (md) {
md.master.reload = reload; md.master.reload = reload;
@ -540,6 +551,7 @@ export const TableList: FC<TableListProp> = ({
if (!isEditor) { if (!isEditor) {
if (local.status === "loading") { if (local.status === "loading") {
if (local.should_toast) {
toast.dismiss(); toast.dismiss();
toast.loading( toast.loading(
<> <>
@ -550,6 +562,9 @@ export const TableList: FC<TableListProp> = ({
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;
} }
`; `;

View File

@ -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;

View File

@ -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();
}, },
}, },
]; ];

View File

@ -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();

View File

@ -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 /> <PanelResizeHandle />
<Panel <Panel
className="c-flex c-flex-col c-items-stretch c-w-10" className="c-flex c-flex-col c-items-stretch c-w-10"
defaultSize={ defaultSize={
parseInt(localStorage.getItem(`prasi-md-h-${md.name}`) || "") || Number(
undefined localStorage.getItem(
`prasi-md-${getPathname({ hash: false })}${md.name}`
)
) || md.detail_size
} }
onResize={(e) => { onResize={(e) => {
if (e < 80) { if (e < 80 && !isEditor) {
localStorage.setItem(`prasi-md-h-${md.name}`, e.toString()); localStorage.setItem(
`prasi-md-${getPathname({ hash: false })}${md.name}`,
e.toString()
);
} }
}} }}
> >
<MDDetail md={md} mdr={mdr} /> <MDDetail md={md} mdr={mdr} />
</Panel> </Panel>
</> </>
)}
</PanelGroup> </PanelGroup>
</div> </div>
); );

View File

@ -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 /> <PanelResizeHandle />
<Panel <Panel
className="c-flex c-flex-col c-items-stretch" className="c-flex c-flex-col c-items-stretch"
defaultSize={ defaultSize={
parseInt(localStorage.getItem(`prasi-md-h-${md.name}`) || "") || Number(
undefined localStorage.getItem(
`prasi-md-${getPathname({ hash: false })}${md.name}`
)
) || md.detail_size
} }
onResize={(e) => { onResize={(e) => {
if (e < 80) { if (e < 80 && !isEditor) {
localStorage.setItem(`prasi-md-h-${md.name}`, e.toString()); localStorage.setItem(
`prasi-md-${getPathname({ hash: false })}${md.name}`,
e.toString()
);
} }
}} }}
> >
<MDDetail md={md} mdr={mdr} /> <MDDetail md={md} mdr={mdr} />
</Panel> </Panel>
</> </>
)}
</PanelGroup> </PanelGroup>
</div> </div>
); );

View File

@ -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) {

View File

@ -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
};`; };`;

47
comps/ui/toast.tsx Executable file
View File

@ -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);
},
};