This commit is contained in:
rizky 2024-08-08 23:32:30 -07:00
parent 4a3374701c
commit 1d5cb53360
8 changed files with 214 additions and 412 deletions

View File

@ -14,7 +14,7 @@ export const filterWhere = (filter_name: string, p: any) => {
}
for (const pf of Object.values(f.filter.ref)) {
if (pf.mode === "raw") {
const data = pf.data?._where ? pf.data?._where : pf.data
const data = pf.data?._where ? pf.data?._where : pf.data;
for (const [k, v] of Object.entries(data)) {
where[k] = v;
}
@ -34,6 +34,5 @@ export const filterWhere = (filter_name: string, p: any) => {
type: p.sft__type,
});
}
console.log({where})
return where;
};

View File

@ -286,171 +286,6 @@ export const TableEdit: FC<{
</div>
</>
);
return (
<>
<div className="c-w-full c-h-full c-flex c-flex-col">
<div
className={cx(
"c-w-full",
css`
.rdg {
overflow-y: hidden !important;
height: var(--rdg-scroll-height) !important;
}
.rdg-cell > div {
flex-direction: row;
align-items: center;
padding-right: 5px;
.field {
flex: 1;
padding-top: 0px;
}
}
.field-error {
display: none;
}
.rdg-header-row {
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
.table-list-inner {
position: relative !important;
}
.typeahead-arrow {
margin-right: 10px;
}
`,
value.length === 0 &&
(show_header === "n"
? css`
display: none;
`
: css`
min-height: 35px;
`),
show_header === "n" &&
css`
.rdg-header-row {
display: none;
}
`
)}
ref={ref}
>
<TableList
row_height={(row) => {
let h = 50;
if (local.tbl) {
const data = local.tbl.data;
const el = local.tbl.el as HTMLDivElement;
let idx = 0;
if (Array.isArray(data)) {
for (let k = 0; k < data.length; k++) {
if (data[k] === row) {
idx = k;
break;
}
}
}
const rowdiv = el.querySelectorAll(`.rdg-row`)[
idx
] as HTMLDivElement;
if (rowdiv) {
rowdiv.querySelectorAll(".field").forEach((field) => {
const div = field as HTMLDivElement;
h = Math.max(h, div.offsetHeight + 10);
});
}
}
return h;
}}
feature={[]}
child={child}
PassProp={PassProp}
name={""}
value={value}
on_init={(tbl) => {
local.tbl = tbl;
local.render();
}}
mode={"table"}
_item={item}
gen_fields={[]}
row_click={({ event }) => {
event.preventDefault();
event.stopPropagation();
}}
show_header={show_header === "y"}
selected={() => {
return false;
}}
filter_name={""}
render_col={(arg) => {
const { props, tbl, child } = arg;
const fm_row = { ...fm, render: local.render };
fm_row.data = props.row;
local.tbl = tbl;
const key = props.column.key;
return (
<PassProp
idx={props.rowIdx}
row={props.row}
col={{
name: key,
value: props.row[props.column.key],
depth: props.row.__depth || 0,
}}
rows={tbl.data}
fm={fm_row}
fm_parent={parent}
ext_fm={{
idx: props.rowIdx,
change: () => {},
remove: () => {
fm.data[name] = tbl.data.filter(
(e: any) => e !== props.row
);
fm.render();
},
add: (e: any) => {
tbl.data.push(e ? e : {});
fm.render();
},
}}
>
{child}
</PassProp>
);
}}
/>
</div>
<PassProp
ext_fm={{
add: (e: any) => {
local.tbl.data.push(e ? e : {});
fm.render();
setTimeout(() => {
const last = Array.from(
ref.current?.querySelectorAll(".rdg-row") || []
).pop();
const input = last?.querySelector("input");
if (input) {
input.focus();
}
}, 100);
},
}}
fm_parent={parent}
>
{bottom}
</PassProp>
</div>
</>
);
};
function getProp(child: any, name: string, defaultValue?: any) {

View File

@ -50,13 +50,6 @@ export const genTableEdit = async (
},
false
);
let tree_depth = "";
let tree_depth_built = "";
if (first) {
tree_depth = `tree_depth={col.depth}`;
tree_depth_built = `tree_depth:col.depth`;
first = false;
}
childs.push({
component: {
id: "297023a4-d552-464a-971d-f40dcd940b77",

View File

@ -4,7 +4,13 @@ import { fields_map } from "@/utils/format-value";
import { useLocal } from "@/utils/use-local";
import { set } from "lib/utils/set";
import get from "lodash.get";
import { AlertTriangle, Loader2, Sticker } from "lucide-react";
import {
AlertTriangle,
ChevronDown,
ChevronRight,
Loader2,
Sticker,
} from "lucide-react";
import {
ChangeEvent,
FC,
@ -31,6 +37,7 @@ import { MDLocal } from "../md/utils/typings";
import { Skeleton } from "../ui/skeleton";
import { sortTree } from "./utils/sort-tree";
import { toast } from "../ui/toast";
import { Arrow } from "../custom/Datepicker/components/utils";
type OnRowClick = (arg: {
row: any;
@ -150,6 +157,7 @@ export const TableList: FC<TableListProp> = ({
| "init"
| "error",
where: null as any,
firstKey: "",
should_toast: true,
paging: {
take: 0,
@ -165,6 +173,7 @@ export const TableList: FC<TableListProp> = ({
}
},
},
collapsed: new Set<number>(),
cached_row: new WeakMap<any, ReactElement>(),
sort: {
columns: (ls_sort?.columns || []) as SortColumn[],
@ -421,10 +430,73 @@ export const TableList: FC<TableListProp> = ({
}
let columns: ColumnOrColumnGroup<any>[] = [];
let isCheckbox = false;
let isTree = false;
try {
if (feature?.find((e) => e === "checkbox")) isCheckbox = true;
if (feature?.find((e) => e === "tree")) isTree = true;
} catch (e) {}
if (local.status === "init") {
const fields = parseGenField(gen_fields);
for (const field of fields) {
if (field.is_pk) {
local.pk = field;
}
}
}
if (typeof value !== "undefined") {
local.data = value;
local.status = "ready" as any;
} else {
if (isEditor && local.status !== "ready") {
if (local.data.length === 0) {
const load_args: any = {
async reload() {},
where: {},
paging: {
take: local.paging.take > 0 ? local.paging.take : undefined,
skip: local.paging.skip,
},
};
if (id_parent) load_args.paging = {};
if (typeof on_load === "function") {
let res = on_load({ ...load_args, mode: "query" }) as any;
if (typeof res === "object" && res instanceof Promise) {
res.then((e) => {
local.data = e;
});
} else {
local.data = res;
}
}
}
local.status = "ready";
}
}
let data = Array.isArray(local.data) ? local.data : [];
if (typeof local.data === "string") console.error(local.data);
if (isEditor) {
if (data.length > 0) {
w.prasi_table_list_temp_data = data;
} else if (
w.prasi_table_list_temp_data &&
w.prasi_table_list_temp_data.length > 0
) {
data = w.prasi_table_list_temp_data;
}
}
if (isTree && id_parent && local.pk && local.sort.columns.length === 0) {
data = sortTree(local.data, id_parent, local.pk.name).filter((e) => {
if (local.pk && local.collapsed.has(e?.__parent?.[local.pk.name])) {
return false;
}
return true;
});
}
if (childs.length && isCheckbox) {
columns.push({
key: SELECT_COLUMN_KEY,
@ -466,13 +538,14 @@ export const TableList: FC<TableListProp> = ({
cellClass: selectCellClassname,
});
}
let first = true;
for (const child of childs) {
let key = getProp(child, "name", {});
const name = getProp(child, "title", "");
const type = getProp(child, "type", "");
const width = parseInt(getProp(child, "width", {}));
if (type === "checkbox") {
const on_click = getProp(child, "opt__on_click", "");
columns.push({
key,
name,
@ -528,6 +601,58 @@ export const TableList: FC<TableListProp> = ({
});
return (
<>
{isTree && local.firstKey === key && local.pk && (
<div
className={cx(
css`
padding-left: ${3 + props.row.__depth * 8}px;
`,
"c-flex c-items-center c-cursor-pointer"
)}
onClick={(e) => {
if (!local.pk) return;
if (props?.row?.__children?.length > 0) {
e.stopPropagation();
if (!local.collapsed.has(props.row?.[local.pk.name])) {
local.collapsed.add(props.row?.[local.pk.name]);
} else {
local.collapsed.delete(props.row?.[local.pk.name]);
}
local.render();
}
}}
>
<div
className={cx(
css`
width: 16px;
`
)}
>
{props.row?.__children?.length > 0 && (
<>
{local.collapsed.has(props.row?.[local.pk.name]) ? (
<ChevronRight size={16} />
) : (
<ChevronDown size={16} />
)}
</>
)}
</div>
{props.row?.__parent &&
props.row?.__children?.length === 0 && (
<div
className={cx(
" c-border-l c-border-b c-border-black c-w-[10px] c-h-[15px]",
css`
margin-top: -10px;
`
)}
></div>
)}
</div>
)}
<PassProp
idx={props.rowIdx}
row={props.row}
@ -540,9 +665,15 @@ export const TableList: FC<TableListProp> = ({
>
{child}
</PassProp>
</>
);
},
});
if (first) {
first = false;
local.firstKey = key;
}
}
}
if (mode === "list") {
@ -585,63 +716,6 @@ export const TableList: FC<TableListProp> = ({
}
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
if (local.status === "init") {
const fields = parseGenField(gen_fields);
for (const field of fields) {
if (field.is_pk) {
local.pk = field;
}
}
}
if (typeof value !== "undefined") {
local.data = value;
local.status = "ready" as any;
} else {
if (isEditor && local.status !== "ready") {
if (local.data.length === 0) {
const load_args: any = {
async reload() {},
where: {},
paging: {
take: local.paging.take > 0 ? local.paging.take : undefined,
skip: local.paging.skip,
},
};
if (id_parent) load_args.paging = {};
if (typeof on_load === "function") {
let res = on_load({ ...load_args, mode: "query" }) as any;
if (typeof res === "object" && res instanceof Promise) {
res.then((e) => {
local.data = e;
});
} else {
local.data = res;
}
}
}
local.status = "ready";
}
}
let data = Array.isArray(local.data) ? local.data : [];
if (typeof local.data === "string") console.error(local.data);
if (isEditor) {
if (data.length > 0) {
w.prasi_table_list_temp_data = data;
} else if (
w.prasi_table_list_temp_data &&
w.prasi_table_list_temp_data.length > 0
) {
data = w.prasi_table_list_temp_data;
}
}
if (id_parent && local.pk && local.sort.columns.length === 0) {
data = sortTree(local.data, id_parent, local.pk.name);
}
if (mode === "table") {
return (
<div
@ -651,6 +725,17 @@ export const TableList: FC<TableListProp> = ({
css`
.rdg {
display: grid !important;
.rdg-cell,
.rdg-header-sort-name {
display: flex;
flex-direction: row;
align-items: stretch;
&.rdg-header-sort-name {
align-items: center;
}
}
}
`
)}

View File

@ -42,158 +42,53 @@ export const treePrefix = (props: any) => {
}
return prefix;
};
export const sortTree = (list: any[], parent_key: string, pk: string) => {
const nodes: { [id: string]: any } = {};
const result: any[] = [];
// First pass: Create nodes
list.forEach((node) => {
const id = node[pk];
nodes[id] = { ...node, __depth: 0, __children: [] };
nodes[id] = { ...node, __depth: 0, __children: [], __parent: null };
});
// Second pass: Build the tree structure
// Second pass: Build relationships
list.forEach((node) => {
const id = node[pk];
const parentId = node[parent_key];
if (parentId === null || parentId === undefined) {
result.push(nodes[id]);
} else {
if (nodes[parentId]) {
if (parentId && parentId !== id && nodes[parentId]) {
nodes[id].__parent = nodes[parentId];
nodes[parentId].__children.push(nodes[id]);
} else {
// Handle the case where a parent is missing
result.push(nodes[id]);
}
}
});
// Function to flatten the tree
function flattenTree(node: any, depth: number = 0): any[] {
node.__depth = depth;
const children = node.__children || [];
delete node.__children;
return [
node,
...children
.sort((a: any, b: any) => {
if (
a.__children.length === 0 &&
b.__children.length === 0 &&
a.name &&
b.name
) {
// Function to calculate depth
const calculateDepth = (node: any, visited: Set<string> = new Set()): number => {
if (visited.has(node.id)) return 0; // Prevent cycles
visited.add(node.id);
if (!node.__parent) return 0;
return 1 + calculateDepth(node.__parent, visited);
};
// Calculate depths
Object.values(nodes).forEach((node: any) => {
node.__depth = calculateDepth(node);
});
// Sort nodes
const sortedNodes = Object.values(nodes).sort((a: any, b: any) => {
if (a.__depth !== b.__depth) return a.__depth - b.__depth;
if (a.__children.length !== b.__children.length) {
return b.__children.length - a.__children.length;
}
return a.name.localeCompare(b.name);
}
});
return (b.__children?.length || 0) - (a.__children?.length || 0);
})
.flatMap((child: any) => flattenTree(child, depth + 1)),
];
}
// Flatten and assign indices
const flatResult = result.flatMap((node) => flattenTree(node));
flatResult.forEach((node, index) => {
// Assign indices
sortedNodes.forEach((node: any, index: number) => {
node.idx = index;
});
return flatResult;
return sortedNodes;
};
// export const sortTree = (list: any[], parent_key: string, pk: string) => {
// let meta = {} as Record<
// string,
// { item: any; idx: string; depth: number; id_parent: any }
// >;
// let mode = "" as "" | "str" | "num";
// let _list = list.sort((a, b) => {
// if (!mode) {
// mode = typeof a[pk] === "string" ? "str" : "num";
// }
// if (mode === "str") return b[pk].toLocaleString(a[pk]);
// return a[pk] - b[pk];
// });
// if (_list.length > 0 && !isEditor) {
// const new_list = [];
// const unlisted = {} as Record<string, any>;
// for (const item of _list) {
// if (item[parent_key] === null) {
// if (!meta[item[pk]]) {
// meta[item[pk]] = {
// item,
// idx: new_list.length + "",
// depth: 0,
// id_parent: null,
// };
// item.__depth = 0;
// new_list.push(item);
// }
// } else {
// unlisted[item[pk]] = item;
// }
// }
// let cyclic = {} as Record<string, number>;
// while (Object.values(unlisted).length > 0) {
// for (const item of Object.values(unlisted)) {
// const parent = meta[item[parent_key]];
// if (!cyclic[item[pk]]) {
// cyclic[item[pk]] = 1;
// } else {
// cyclic[item[pk]]++;
// }
// if (cyclic[item[pk]] > 5) {
// item.__depth = 0;
// meta[item[pk]] = {
// item,
// depth: 0,
// idx: new_list.length + "",
// id_parent: null,
// };
// new_list.push(item);
// delete unlisted[item[pk]];
// continue;
// }
// if (item[parent_key] === item[pk]) {
// item.__depth = 0;
// meta[item[pk]] = {
// item,
// depth: 0,
// idx: new_list.length + "",
// id_parent: null,
// };
// new_list.push(item);
// delete unlisted[item[pk]];
// continue;
// }
// if (parent) {
// item.__depth = parent.depth + 1;
// meta[item[pk]] = {
// item,
// depth: parent.depth + 1,
// idx: parent.idx + ".",
// id_parent: item[parent_key],
// };
// delete unlisted[item[pk]];
// }
// }
// }
// const sorted = Object.values(meta)
// .sort((a, b) => a.idx.localeCompare(b.idx))
// .map((e) => e.item);
// return sorted;
// }
// return _list;
// };

View File

@ -181,13 +181,6 @@ const genTable = async (opt: GenOpt) => {
return;
}
if (e.is_pk && (arg.mode === "table" || arg.mode === "auto")) return;
let tree_depth = "";
let tree_depth_built = "";
if (first) {
tree_depth = `tree_depth={col.depth}`;
tree_depth_built = `tree_depth:col.depth`;
first = false;
}
return {
component: {
id: "297023a4-d552-464a-971d-f40dcd940b77",
@ -211,10 +204,10 @@ const genTable = async (opt: GenOpt) => {
adv: {
js: `\
<div {...props} className={cx(props.className, \`s-\${_item?.edit?.parent?.item?.id}\` , "table-col")}>
<FormatValue value={col.value} name={col.name} gen_fields={gen__fields} ${tree_depth} />
<FormatValue value={col.value} name={col.name} gen_fields={gen__fields} />
</div>`,
jsBuilt: `\
render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, \`s-\${_item?.edit?.parent?.item?.id}\` , "") }),React.createElement(FormatValue, { value: col.value, name: col.name, gen_fields: gen__fields, ${tree_depth_built} })));
render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, \`s-\${_item?.edit?.parent?.item?.id}\` , "") }),React.createElement(FormatValue, { value: col.value, name: col.name, gen_fields: gen__fields })));
`,
},
}),

View File

@ -55,6 +55,10 @@ async (arg: TableOnLoad) => {
const fields = parseGenField(gen__fields);
const gen = generateSelect(fields);
if (opt__feature.includes("tree") && opt__id_parent) {
gen.select[opt__id_parent] = true
}
const result = {items: []}
result.items = await db.${table}.findMany({
select: gen.select,

View File

@ -11,10 +11,9 @@ export const FormatValue: FC<{
value: any;
name: string;
gen_fields: string[];
tree_depth?: number;
mode?: "money" | "datetime" | "timeago" | "date";
}> = (prop) => {
const { value, gen_fields, name, tree_depth, mode } = prop;
const { value, gen_fields, name, mode } = prop;
if (gen_fields) {
const gf = JSON.stringify(gen_fields);
if (!fields_map.has(gf)) {
@ -122,29 +121,28 @@ export const FormatValue: FC<{
}
}
let prefix = <></>;
if (typeof tree_depth === "number" && tree_depth > 0) {
prefix = (
<div
className={css`
padding-left: ${tree_depth * 5}px;
`}
>
<div
className={cx(
" c-border-l c-border-b c-border-black c-w-[10px] c-h-[15px]",
css`
margin-top: -10px;
`
)}
></div>
</div>
);
}
// let prefix = <></>;
// if (typeof tree_depth === "number" && tree_depth > 0) {
// prefix = (
// <div
// className={css`
// padding-left: ${tree_depth * 5}px;
// `}
// >
// <div
// className={cx(
// " c-border-l c-border-b c-border-black c-w-[10px] c-h-[15px]",
// css`
// margin-top: -10px;
// `
// )}
// ></div>
// </div>
// );
// }
return (
<div className="c-flex c-space-x-2 c-items-center">
{prefix}
<div>{value}</div>
</div>
);