wip fix
This commit is contained in:
parent
6c17b8711d
commit
59f10f944a
|
|
@ -1,16 +1,29 @@
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import DataGrid, { ColumnOrColumnGroup } from "react-data-grid";
|
import DataGrid, { ColumnOrColumnGroup, SortColumn } from "react-data-grid";
|
||||||
import "react-data-grid/lib/styles.css";
|
import "react-data-grid/lib/styles.css";
|
||||||
import { getProp } from "../md/utils/get-prop";
|
import { getProp } from "../md/utils/get-prop";
|
||||||
|
import { Toaster, toast } from "sonner";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { cn } from "@/utils";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { fields_map } from "@/utils/format-value";
|
||||||
|
import { Skeleton } from "../ui/skeleton";
|
||||||
|
|
||||||
type TableListProp = {
|
type TableListProp = {
|
||||||
child: any;
|
child: any;
|
||||||
PassProp: any;
|
PassProp: any;
|
||||||
name: string;
|
name: string;
|
||||||
on_load: () => Promise<any[]>;
|
on_load: (arg: {
|
||||||
|
reload: () => Promise<void>;
|
||||||
|
orderBy?: Record<string, "asc" | "desc" | Record<string, "asc" | "desc">>;
|
||||||
|
paging: { take: number; skip: number };
|
||||||
|
mode: "count" | "query";
|
||||||
|
}) => Promise<any[]>;
|
||||||
mode: "table" | "list" | "grid";
|
mode: "table" | "list" | "grid";
|
||||||
|
_meta: Record<string, any>;
|
||||||
|
gen_fields: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TableList: FC<TableListProp> = ({
|
export const TableList: FC<TableListProp> = ({
|
||||||
|
|
@ -19,6 +32,8 @@ export const TableList: FC<TableListProp> = ({
|
||||||
child,
|
child,
|
||||||
PassProp,
|
PassProp,
|
||||||
mode,
|
mode,
|
||||||
|
_meta,
|
||||||
|
gen_fields,
|
||||||
}) => {
|
}) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
el: null as null | HTMLDivElement,
|
el: null as null | HTMLDivElement,
|
||||||
|
|
@ -27,30 +42,97 @@ export const TableList: FC<TableListProp> = ({
|
||||||
rob: new ResizeObserver(([e]) => {
|
rob: new ResizeObserver(([e]) => {
|
||||||
local.height = e.contentRect.height;
|
local.height = e.contentRect.height;
|
||||||
local.width = e.contentRect.width;
|
local.width = e.contentRect.width;
|
||||||
|
if (local.status === "ready") local.status = "resizing";
|
||||||
local.render();
|
local.render();
|
||||||
}),
|
}),
|
||||||
scrolled: false,
|
scrolled: false,
|
||||||
data: [] as any[],
|
data: [] as any[],
|
||||||
status: "ready" as "loading" | "ready",
|
status: "init" as "loading" | "ready" | "resizing" | "reload" | "init",
|
||||||
|
paging: {
|
||||||
|
take: 0,
|
||||||
|
skip: 0,
|
||||||
|
timeout: null as any,
|
||||||
|
total: 0,
|
||||||
|
scroll: (event: React.UIEvent<HTMLDivElement>) => {
|
||||||
|
if (local.status === "loading" || !isAtBottom(event)) return;
|
||||||
|
if (local.data.length >= local.paging.skip + local.paging.take) {
|
||||||
|
local.paging.skip += local.paging.take;
|
||||||
|
local.status = "reload";
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
columns: [] as SortColumn[],
|
||||||
|
on_change: (cols: SortColumn[]) => {
|
||||||
|
local.sort.columns = cols;
|
||||||
|
local.paging.skip = 0;
|
||||||
|
|
||||||
|
if (cols.length > 0) {
|
||||||
|
const { columnKey, direction } = cols[0];
|
||||||
|
|
||||||
|
let should_set = true;
|
||||||
|
const fields = fields_map.get(gen_fields);
|
||||||
|
if (fields) {
|
||||||
|
const rel = fields?.find((e) => e.name === columnKey);
|
||||||
|
if (rel && rel.checked) {
|
||||||
|
const field = rel.checked.find((e) => !e.is_pk);
|
||||||
|
if (field) {
|
||||||
|
should_set = false;
|
||||||
|
local.sort.orderBy = {
|
||||||
|
[columnKey]: {
|
||||||
|
[field.name]: direction === "ASC" ? "asc" : "desc",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_set) {
|
||||||
|
local.sort.orderBy = {
|
||||||
|
[columnKey]: direction === "ASC" ? "asc" : "desc",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
local.sort.orderBy = null;
|
||||||
|
}
|
||||||
|
local.status = "reload";
|
||||||
|
local.render();
|
||||||
|
},
|
||||||
|
orderBy: null as null | Record<
|
||||||
|
string,
|
||||||
|
"asc" | "desc" | Record<string, "asc" | "desc">
|
||||||
|
>,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (local.status === "ready") {
|
(async () => {
|
||||||
|
if (local.status === "reload") {
|
||||||
local.status = "loading";
|
local.status = "loading";
|
||||||
local.render();
|
local.render();
|
||||||
|
const orderBy = local.sort.orderBy || undefined;
|
||||||
|
const load_args = {
|
||||||
|
async reload() {},
|
||||||
|
orderBy,
|
||||||
|
paging: { take: local.paging.take, skip: local.paging.skip },
|
||||||
|
};
|
||||||
|
|
||||||
const result = on_load();
|
const result = on_load({ ...load_args, mode: "query" });
|
||||||
const callback = (data: any[]) => {
|
const callback = (data: any[]) => {
|
||||||
|
if (local.paging.skip === 0) {
|
||||||
local.data = data;
|
local.data = data;
|
||||||
|
} else {
|
||||||
|
local.data = [...local.data, ...data];
|
||||||
|
}
|
||||||
|
local.status = "ready";
|
||||||
local.render();
|
local.render();
|
||||||
};
|
};
|
||||||
if (result instanceof Promise) result.then(callback);
|
if (result instanceof Promise) result.then(callback);
|
||||||
else callback(result);
|
else callback(result);
|
||||||
|
|
||||||
local.status = "ready";
|
|
||||||
local.render();
|
|
||||||
}
|
}
|
||||||
}, [on_load]);
|
})();
|
||||||
|
}, [local.status, on_load, local.sort.orderBy]);
|
||||||
|
|
||||||
const raw_childs = get(
|
const raw_childs = get(
|
||||||
child,
|
child,
|
||||||
|
|
@ -60,11 +142,12 @@ export const TableList: FC<TableListProp> = ({
|
||||||
let childs: any[] = [];
|
let childs: any[] = [];
|
||||||
|
|
||||||
const mode_child = raw_childs.find((e: any) => e.name === mode);
|
const mode_child = raw_childs.find((e: any) => e.name === mode);
|
||||||
|
if (mode_child) {
|
||||||
if (mode_child && mode_child.childs) {
|
const meta = _meta[mode_child.id];
|
||||||
childs = mode_child.childs;
|
if (meta && meta.item.childs) {
|
||||||
|
childs = meta.item.childs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log(raw_childs);
|
|
||||||
|
|
||||||
const columns: ColumnOrColumnGroup<any>[] = [];
|
const columns: ColumnOrColumnGroup<any>[] = [];
|
||||||
for (const child of childs) {
|
for (const child of childs) {
|
||||||
|
|
@ -76,6 +159,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
name,
|
name,
|
||||||
width: width > 0 ? width : undefined,
|
width: width > 0 ? width : undefined,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
sortable: true,
|
||||||
renderCell(props) {
|
renderCell(props) {
|
||||||
return (
|
return (
|
||||||
<PassProp
|
<PassProp
|
||||||
|
|
@ -94,11 +178,115 @@ export const TableList: FC<TableListProp> = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (local.status === "resizing") {
|
||||||
|
local.status = "ready";
|
||||||
|
local.render();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEditor) {
|
||||||
|
if (local.status === "loading") {
|
||||||
|
toast.loading(
|
||||||
|
<>
|
||||||
|
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||||
|
Loading {local.paging.skip === 0 ? "Data" : "more rows"} ...
|
||||||
|
</>,
|
||||||
|
{
|
||||||
|
dismissible: true,
|
||||||
|
className: css`
|
||||||
|
background: #e4f7ff;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (mode === "table") {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"c-w-full c-h-full",
|
"c-w-full c-h-full c-flex-1 c-relative c-overflow-hidden",
|
||||||
css`
|
dataGridStyle(local)
|
||||||
|
)}
|
||||||
|
ref={(el) => {
|
||||||
|
if (!local.el && el) {
|
||||||
|
local.el = el;
|
||||||
|
local.rob.observe(el);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.status !== "ready" && (
|
||||||
|
<div className="c-flex c-flex-col c-space-y-2 c-m-4 c-absolute c-left-0 c-top-0">
|
||||||
|
<Skeleton className={cx("c-w-[200px] c-h-[11px]")} />
|
||||||
|
<Skeleton className={cx("c-w-[170px] c-h-[11px]")} />
|
||||||
|
<Skeleton className={cx("c-w-[180px] c-h-[11px]")} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="c-absolute c-inset-0">
|
||||||
|
{toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
|
||||||
|
{local.status === "init" ? (
|
||||||
|
<DataGrid
|
||||||
|
style={{ opacity: 0 }}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
key: "_",
|
||||||
|
name: "",
|
||||||
|
renderCell({ rowIdx }) {
|
||||||
|
if (local.paging.take < rowIdx) {
|
||||||
|
local.paging.take = rowIdx;
|
||||||
|
}
|
||||||
|
clearTimeout(local.paging.timeout);
|
||||||
|
local.paging.timeout = setTimeout(() => {
|
||||||
|
local.status = "reload";
|
||||||
|
local.paging.take = local.paging.take * 5;
|
||||||
|
local.render();
|
||||||
|
}, 100);
|
||||||
|
return <></>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rows={genRows(200)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<DataGrid
|
||||||
|
sortColumns={local.sort.columns}
|
||||||
|
onSortColumnsChange={local.sort.on_change}
|
||||||
|
columns={columns}
|
||||||
|
rows={local.data || []}
|
||||||
|
onScroll={local.paging.scroll}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const genRows = (total: number) => {
|
||||||
|
const result = [] as any[];
|
||||||
|
for (let i = 0; i < total; i++) {
|
||||||
|
result.push({ _: i });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataGridStyle = (local: { height: number }) => css`
|
||||||
|
.rdg {
|
||||||
|
block-size: ${local.height}px;
|
||||||
|
}
|
||||||
div[role="row"]:hover {
|
div[role="row"]:hover {
|
||||||
background: #e2f1ff;
|
background: #e2f1ff;
|
||||||
.num-edit {
|
.num-edit {
|
||||||
|
|
@ -114,20 +302,18 @@ export const TableList: FC<TableListProp> = ({
|
||||||
div[aria-selected="true"] {
|
div[aria-selected="true"] {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
div[role="gridcell"] {
|
||||||
|
padding-inline: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.row-selected {
|
.row-selected {
|
||||||
background: #e2f1ff;
|
background: #e2f1ff;
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
)}
|
|
||||||
ref={(el) => {
|
function isAtBottom({ currentTarget }: React.UIEvent<HTMLDivElement>): boolean {
|
||||||
if (!local.el && el) {
|
return (
|
||||||
local.el = el;
|
currentTarget.scrollTop + 10 >=
|
||||||
local.rob.observe(el);
|
currentTarget.scrollHeight - currentTarget.clientHeight
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DataGrid columns={columns} rows={local.data || []} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,20 +60,43 @@ export const gen_table_list = (
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
result["child"].content.childs = [
|
const child = createItem({
|
||||||
createItem({
|
|
||||||
name: arg.mode,
|
name: arg.mode,
|
||||||
childs: [
|
childs: columns
|
||||||
{
|
.map((e) => {
|
||||||
|
if (e.is_pk) return;
|
||||||
|
return {
|
||||||
component: {
|
component: {
|
||||||
id: "297023a4-d552-464a-971d-f40dcd940b77",
|
id: "297023a4-d552-464a-971d-f40dcd940b77",
|
||||||
props: {
|
props: {
|
||||||
name: "muku",
|
name: e.name,
|
||||||
|
title: formatName(e.name),
|
||||||
|
child: {
|
||||||
|
name: "cell",
|
||||||
|
padding: {
|
||||||
|
l: 8,
|
||||||
|
b: 0,
|
||||||
|
t: 0,
|
||||||
|
r: 8,
|
||||||
|
},
|
||||||
|
adv: {
|
||||||
|
js: `\
|
||||||
|
<div {...props} className={cx(props.className, "")}>
|
||||||
|
<FormatValue value={col.value} name={col.name} gen_fields={gen_fields} />
|
||||||
|
</div>`,
|
||||||
|
jsBuilt: `\
|
||||||
|
render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }),React.createElement(FormatValue, { value: col.value, name: col.name, gen_fields: gen_fields })));
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
}),
|
};
|
||||||
|
})
|
||||||
|
.filter((e) => e) as any,
|
||||||
|
});
|
||||||
|
result["child"].content.childs = [
|
||||||
|
child,
|
||||||
...result["child"].content.childs,
|
...result["child"].content.childs,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,20 +29,30 @@ export const on_load = ({
|
||||||
|
|
||||||
return `\
|
return `\
|
||||||
async (arg: TableOnLoad) => {
|
async (arg: TableOnLoad) => {
|
||||||
if (isEditor) return [${JSON.stringify(sample)}];
|
if (isEditor) return sampleData;
|
||||||
|
|
||||||
|
if (arg.mode === 'count') {
|
||||||
|
return await db.${table}.count();
|
||||||
|
}
|
||||||
|
|
||||||
const items = await db.${table}.findMany({
|
const items = await db.${table}.findMany({
|
||||||
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
|
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
|
||||||
orderBy: {
|
orderBy: arg.orderBy || {
|
||||||
${pk}: "desc"
|
${pk}: "desc"
|
||||||
}
|
},
|
||||||
|
...arg.paging,
|
||||||
});
|
});
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sampleData = [${JSON.stringify(sample, null, 2)}]
|
||||||
|
|
||||||
type TableOnLoad = {
|
type TableOnLoad = {
|
||||||
reload: () => Promise<void>;
|
reload: () => Promise<void>;
|
||||||
|
orderBy?: Record<string, "asc" | "desc">;
|
||||||
|
paging: { take: number; skip: number };
|
||||||
|
mode: 'count' | 'query'
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
33
gen/utils.ts
33
gen/utils.ts
|
|
@ -22,8 +22,13 @@ export const formatName = (name: string) => {
|
||||||
|
|
||||||
type SimplifiedItem = {
|
type SimplifiedItem = {
|
||||||
name?: string;
|
name?: string;
|
||||||
component?: { id: string; props: Record<string, any> };
|
component?: { id: string; props: Record<string, string | SimplifiedItem> };
|
||||||
childs?: SimplifiedItem[];
|
childs?: SimplifiedItem[];
|
||||||
|
adv?: {
|
||||||
|
js: string;
|
||||||
|
jsBuilt: string;
|
||||||
|
};
|
||||||
|
padding?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createItem = (arg: SimplifiedItem): any => {
|
export const createItem = (arg: SimplifiedItem): any => {
|
||||||
|
|
@ -34,6 +39,26 @@ export const createItem = (arg: SimplifiedItem): any => {
|
||||||
|
|
||||||
if (arg.component.props) {
|
if (arg.component.props) {
|
||||||
for (const [k, v] of Object.entries(arg.component.props)) {
|
for (const [k, v] of Object.entries(arg.component.props)) {
|
||||||
|
if (typeof v === "object") {
|
||||||
|
component.props[k] = {
|
||||||
|
meta: {
|
||||||
|
type: "content-element",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
id: createId(),
|
||||||
|
dim: {
|
||||||
|
h: "full",
|
||||||
|
w: "full",
|
||||||
|
},
|
||||||
|
padding: arg.padding,
|
||||||
|
type: "item",
|
||||||
|
name: k,
|
||||||
|
...v,
|
||||||
|
},
|
||||||
|
value: "",
|
||||||
|
valueBuilt: "",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
component.props[k] = {
|
component.props[k] = {
|
||||||
type: "string",
|
type: "string",
|
||||||
value: JSON.stringify(v),
|
value: JSON.stringify(v),
|
||||||
|
|
@ -42,6 +67,7 @@ export const createItem = (arg: SimplifiedItem): any => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: createId(),
|
id: createId(),
|
||||||
|
|
@ -49,10 +75,13 @@ export const createItem = (arg: SimplifiedItem): any => {
|
||||||
h: "full",
|
h: "full",
|
||||||
w: "full",
|
w: "full",
|
||||||
},
|
},
|
||||||
|
padding: arg.padding,
|
||||||
name: arg.name || "item",
|
name: arg.name || "item",
|
||||||
type: "item",
|
type: "item",
|
||||||
component,
|
component,
|
||||||
script: {},
|
script: {
|
||||||
|
...arg.adv,
|
||||||
|
},
|
||||||
childs: arg.childs?.map(createItem),
|
childs: arg.childs?.map(createItem),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { GFCol } from "@/gen/utils";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
export const fields_map = new WeakMap<
|
||||||
|
string[],
|
||||||
|
(GFCol & { checked?: GFCol[] })[]
|
||||||
|
>();
|
||||||
|
|
||||||
|
export const FormatValue: FC<{
|
||||||
|
value: any;
|
||||||
|
name: string;
|
||||||
|
gen_fields: string[];
|
||||||
|
}> = (prop) => {
|
||||||
|
const { value, gen_fields, name } = prop;
|
||||||
|
|
||||||
|
if (!fields_map.has(gen_fields)) {
|
||||||
|
fields_map.set(
|
||||||
|
gen_fields,
|
||||||
|
gen_fields.map((e: any) => {
|
||||||
|
if (typeof e === "string") {
|
||||||
|
return JSON.parse(e);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...JSON.parse(e.value),
|
||||||
|
checked: e.checked.map(JSON.parse),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = fields_map.get(gen_fields);
|
||||||
|
|
||||||
|
if (typeof value === "object" && value) {
|
||||||
|
const rel = fields?.find((e) => e.name === name);
|
||||||
|
if (rel && rel.checked) {
|
||||||
|
const result = rel.checked
|
||||||
|
.filter((e) => !e.is_pk)
|
||||||
|
.map((e) => {
|
||||||
|
return value[e.name];
|
||||||
|
})
|
||||||
|
.join(" - ");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
return <>{value}</>;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue