wip fix
This commit is contained in:
parent
6c17b8711d
commit
59f10f944a
|
|
@ -1,16 +1,29 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import get from "lodash.get";
|
||||
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 { 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 = {
|
||||
child: any;
|
||||
PassProp: any;
|
||||
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";
|
||||
_meta: Record<string, any>;
|
||||
gen_fields: string[];
|
||||
};
|
||||
|
||||
export const TableList: FC<TableListProp> = ({
|
||||
|
|
@ -19,6 +32,8 @@ export const TableList: FC<TableListProp> = ({
|
|||
child,
|
||||
PassProp,
|
||||
mode,
|
||||
_meta,
|
||||
gen_fields,
|
||||
}) => {
|
||||
const local = useLocal({
|
||||
el: null as null | HTMLDivElement,
|
||||
|
|
@ -27,30 +42,97 @@ export const TableList: FC<TableListProp> = ({
|
|||
rob: new ResizeObserver(([e]) => {
|
||||
local.height = e.contentRect.height;
|
||||
local.width = e.contentRect.width;
|
||||
if (local.status === "ready") local.status = "resizing";
|
||||
local.render();
|
||||
}),
|
||||
scrolled: false,
|
||||
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(() => {
|
||||
if (local.status === "ready") {
|
||||
local.status = "loading";
|
||||
local.render();
|
||||
|
||||
const result = on_load();
|
||||
const callback = (data: any[]) => {
|
||||
local.data = data;
|
||||
(async () => {
|
||||
if (local.status === "reload") {
|
||||
local.status = "loading";
|
||||
local.render();
|
||||
};
|
||||
if (result instanceof Promise) result.then(callback);
|
||||
else callback(result);
|
||||
const orderBy = local.sort.orderBy || undefined;
|
||||
const load_args = {
|
||||
async reload() {},
|
||||
orderBy,
|
||||
paging: { take: local.paging.take, skip: local.paging.skip },
|
||||
};
|
||||
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
}
|
||||
}, [on_load]);
|
||||
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) result.then(callback);
|
||||
else callback(result);
|
||||
}
|
||||
})();
|
||||
}, [local.status, on_load, local.sort.orderBy]);
|
||||
|
||||
const raw_childs = get(
|
||||
child,
|
||||
|
|
@ -60,11 +142,12 @@ export const TableList: FC<TableListProp> = ({
|
|||
let childs: any[] = [];
|
||||
|
||||
const mode_child = raw_childs.find((e: any) => e.name === mode);
|
||||
|
||||
if (mode_child && mode_child.childs) {
|
||||
childs = mode_child.childs;
|
||||
if (mode_child) {
|
||||
const meta = _meta[mode_child.id];
|
||||
if (meta && meta.item.childs) {
|
||||
childs = meta.item.childs;
|
||||
}
|
||||
}
|
||||
console.log(raw_childs);
|
||||
|
||||
const columns: ColumnOrColumnGroup<any>[] = [];
|
||||
for (const child of childs) {
|
||||
|
|
@ -76,6 +159,7 @@ export const TableList: FC<TableListProp> = ({
|
|||
name,
|
||||
width: width > 0 ? width : undefined,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
renderCell(props) {
|
||||
return (
|
||||
<PassProp
|
||||
|
|
@ -94,40 +178,142 @@ export const TableList: FC<TableListProp> = ({
|
|||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"c-w-full c-h-full",
|
||||
css`
|
||||
div[role="row"]:hover {
|
||||
background: #e2f1ff;
|
||||
.num-edit {
|
||||
display: flex;
|
||||
}
|
||||
.num-idx {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
div[role="columnheader"] span svg {
|
||||
margin: 12px 2px;
|
||||
}
|
||||
div[aria-selected="true"] {
|
||||
outline: none;
|
||||
}
|
||||
if (local.status === "resizing") {
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
|
||||
.row-selected {
|
||||
background: #e2f1ff;
|
||||
}
|
||||
`
|
||||
)}
|
||||
ref={(el) => {
|
||||
if (!local.el && el) {
|
||||
local.el = el;
|
||||
local.rob.observe(el);
|
||||
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;
|
||||
`,
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DataGrid columns={columns} rows={local.data || []} />
|
||||
</div>
|
||||
);
|
||||
);
|
||||
} 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 (
|
||||
<div
|
||||
className={cx(
|
||||
"c-w-full c-h-full c-flex-1 c-relative c-overflow-hidden",
|
||||
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 {
|
||||
background: #e2f1ff;
|
||||
.num-edit {
|
||||
display: flex;
|
||||
}
|
||||
.num-idx {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
div[role="columnheader"] span svg {
|
||||
margin: 12px 2px;
|
||||
}
|
||||
div[aria-selected="true"] {
|
||||
outline: none;
|
||||
}
|
||||
div[role="gridcell"] {
|
||||
padding-inline: 0px;
|
||||
}
|
||||
|
||||
.row-selected {
|
||||
background: #e2f1ff;
|
||||
}
|
||||
`;
|
||||
|
||||
function isAtBottom({ currentTarget }: React.UIEvent<HTMLDivElement>): boolean {
|
||||
return (
|
||||
currentTarget.scrollTop + 10 >=
|
||||
currentTarget.scrollHeight - currentTarget.clientHeight
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,20 +60,43 @@ export const gen_table_list = (
|
|||
}
|
||||
);
|
||||
|
||||
result["child"].content.childs = [
|
||||
createItem({
|
||||
name: arg.mode,
|
||||
childs: [
|
||||
{
|
||||
const child = createItem({
|
||||
name: arg.mode,
|
||||
childs: columns
|
||||
.map((e) => {
|
||||
if (e.is_pk) return;
|
||||
return {
|
||||
component: {
|
||||
id: "297023a4-d552-464a-971d-f40dcd940b77",
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,20 +29,30 @@ export const on_load = ({
|
|||
|
||||
return `\
|
||||
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({
|
||||
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
|
||||
orderBy: {
|
||||
orderBy: arg.orderBy || {
|
||||
${pk}: "desc"
|
||||
}
|
||||
},
|
||||
...arg.paging,
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
const sampleData = [${JSON.stringify(sample, null, 2)}]
|
||||
|
||||
type TableOnLoad = {
|
||||
reload: () => Promise<void>;
|
||||
orderBy?: Record<string, "asc" | "desc">;
|
||||
paging: { take: number; skip: number };
|
||||
mode: 'count' | 'query'
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
|
|
|||
43
gen/utils.ts
43
gen/utils.ts
|
|
@ -22,8 +22,13 @@ export const formatName = (name: string) => {
|
|||
|
||||
type SimplifiedItem = {
|
||||
name?: string;
|
||||
component?: { id: string; props: Record<string, any> };
|
||||
component?: { id: string; props: Record<string, string | SimplifiedItem> };
|
||||
childs?: SimplifiedItem[];
|
||||
adv?: {
|
||||
js: string;
|
||||
jsBuilt: string;
|
||||
};
|
||||
padding?: any;
|
||||
};
|
||||
|
||||
export const createItem = (arg: SimplifiedItem): any => {
|
||||
|
|
@ -34,11 +39,32 @@ export const createItem = (arg: SimplifiedItem): any => {
|
|||
|
||||
if (arg.component.props) {
|
||||
for (const [k, v] of Object.entries(arg.component.props)) {
|
||||
component.props[k] = {
|
||||
type: "string",
|
||||
value: JSON.stringify(v),
|
||||
valueBuilt: JSON.stringify(v),
|
||||
};
|
||||
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] = {
|
||||
type: "string",
|
||||
value: JSON.stringify(v),
|
||||
valueBuilt: JSON.stringify(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,10 +75,13 @@ export const createItem = (arg: SimplifiedItem): any => {
|
|||
h: "full",
|
||||
w: "full",
|
||||
},
|
||||
padding: arg.padding,
|
||||
name: arg.name || "item",
|
||||
type: "item",
|
||||
component,
|
||||
script: {},
|
||||
script: {
|
||||
...arg.adv,
|
||||
},
|
||||
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