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

View File

@ -286,171 +286,6 @@ export const TableEdit: FC<{
</div> </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) { function getProp(child: any, name: string, defaultValue?: any) {

View File

@ -50,13 +50,6 @@ export const genTableEdit = async (
}, },
false 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({ childs.push({
component: { component: {
id: "297023a4-d552-464a-971d-f40dcd940b77", 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 { useLocal } from "@/utils/use-local";
import { set } from "lib/utils/set"; import { set } from "lib/utils/set";
import get from "lodash.get"; import get from "lodash.get";
import { AlertTriangle, Loader2, Sticker } from "lucide-react"; import {
AlertTriangle,
ChevronDown,
ChevronRight,
Loader2,
Sticker,
} from "lucide-react";
import { import {
ChangeEvent, ChangeEvent,
FC, FC,
@ -31,6 +37,7 @@ 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"; import { toast } from "../ui/toast";
import { Arrow } from "../custom/Datepicker/components/utils";
type OnRowClick = (arg: { type OnRowClick = (arg: {
row: any; row: any;
@ -150,6 +157,7 @@ export const TableList: FC<TableListProp> = ({
| "init" | "init"
| "error", | "error",
where: null as any, where: null as any,
firstKey: "",
should_toast: true, should_toast: true,
paging: { paging: {
take: 0, take: 0,
@ -165,6 +173,7 @@ export const TableList: FC<TableListProp> = ({
} }
}, },
}, },
collapsed: new Set<number>(),
cached_row: new WeakMap<any, ReactElement>(), cached_row: new WeakMap<any, ReactElement>(),
sort: { sort: {
columns: (ls_sort?.columns || []) as SortColumn[], columns: (ls_sort?.columns || []) as SortColumn[],
@ -421,10 +430,73 @@ export const TableList: FC<TableListProp> = ({
} }
let columns: ColumnOrColumnGroup<any>[] = []; let columns: ColumnOrColumnGroup<any>[] = [];
let isCheckbox = false; let isCheckbox = false;
let isTree = false;
try { try {
if (feature?.find((e) => e === "checkbox")) isCheckbox = true; if (feature?.find((e) => e === "checkbox")) isCheckbox = true;
if (feature?.find((e) => e === "tree")) isTree = true;
} catch (e) {} } 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) { if (childs.length && isCheckbox) {
columns.push({ columns.push({
key: SELECT_COLUMN_KEY, key: SELECT_COLUMN_KEY,
@ -466,13 +538,14 @@ export const TableList: FC<TableListProp> = ({
cellClass: selectCellClassname, cellClass: selectCellClassname,
}); });
} }
let first = true;
for (const child of childs) { for (const child of childs) {
let key = getProp(child, "name", {}); let key = getProp(child, "name", {});
const name = getProp(child, "title", ""); const name = getProp(child, "title", "");
const type = getProp(child, "type", ""); const type = getProp(child, "type", "");
const width = parseInt(getProp(child, "width", {})); const width = parseInt(getProp(child, "width", {}));
if (type === "checkbox") { if (type === "checkbox") {
const on_click = getProp(child, "opt__on_click", "");
columns.push({ columns.push({
key, key,
name, name,
@ -528,6 +601,58 @@ export const TableList: FC<TableListProp> = ({
}); });
return ( 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 <PassProp
idx={props.rowIdx} idx={props.rowIdx}
row={props.row} row={props.row}
@ -540,9 +665,15 @@ export const TableList: FC<TableListProp> = ({
> >
{child} {child}
</PassProp> </PassProp>
</>
); );
}, },
}); });
if (first) {
first = false;
local.firstKey = key;
}
} }
} }
if (mode === "list") { if (mode === "list") {
@ -585,63 +716,6 @@ export const TableList: FC<TableListProp> = ({
} }
const toaster_el = document.getElementsByClassName("prasi-toaster")[0]; 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") { if (mode === "table") {
return ( return (
<div <div
@ -651,6 +725,17 @@ export const TableList: FC<TableListProp> = ({
css` css`
.rdg { .rdg {
display: grid !important; 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; return prefix;
}; };
export const sortTree = (list: any[], parent_key: string, pk: string) => { export const sortTree = (list: any[], parent_key: string, pk: string) => {
const nodes: { [id: string]: any } = {}; const nodes: { [id: string]: any } = {};
const result: any[] = [];
// First pass: Create nodes // First pass: Create nodes
list.forEach((node) => { list.forEach((node) => {
const id = node[pk]; 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) => { list.forEach((node) => {
const id = node[pk]; const id = node[pk];
const parentId = node[parent_key]; const parentId = node[parent_key];
if (parentId === null || parentId === undefined) {
result.push(nodes[id]); if (parentId && parentId !== id && nodes[parentId]) {
} else { nodes[id].__parent = nodes[parentId];
if (nodes[parentId]) {
nodes[parentId].__children.push(nodes[id]); 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 to calculate depth
function flattenTree(node: any, depth: number = 0): any[] { const calculateDepth = (node: any, visited: Set<string> = new Set()): number => {
node.__depth = depth; if (visited.has(node.id)) return 0; // Prevent cycles
const children = node.__children || []; visited.add(node.id);
delete node.__children;
return [ if (!node.__parent) return 0;
node, return 1 + calculateDepth(node.__parent, visited);
...children };
.sort((a: any, b: any) => {
if ( // Calculate depths
a.__children.length === 0 && Object.values(nodes).forEach((node: any) => {
b.__children.length === 0 && node.__depth = calculateDepth(node);
a.name && });
b.name
) { // 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 a.name.localeCompare(b.name);
} });
return (b.__children?.length || 0) - (a.__children?.length || 0); // Assign indices
}) sortedNodes.forEach((node: any, index: number) => {
.flatMap((child: any) => flattenTree(child, depth + 1)),
];
}
// Flatten and assign indices
const flatResult = result.flatMap((node) => flattenTree(node));
flatResult.forEach((node, index) => {
node.idx = index; 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; return;
} }
if (e.is_pk && (arg.mode === "table" || arg.mode === "auto")) 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 { return {
component: { component: {
id: "297023a4-d552-464a-971d-f40dcd940b77", id: "297023a4-d552-464a-971d-f40dcd940b77",
@ -211,10 +204,10 @@ const genTable = async (opt: GenOpt) => {
adv: { adv: {
js: `\ js: `\
<div {...props} className={cx(props.className, \`s-\${_item?.edit?.parent?.item?.id}\` , "table-col")}> <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>`, </div>`,
jsBuilt: `\ 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 fields = parseGenField(gen__fields);
const gen = generateSelect(fields); const gen = generateSelect(fields);
if (opt__feature.includes("tree") && opt__id_parent) {
gen.select[opt__id_parent] = true
}
const result = {items: []} const result = {items: []}
result.items = await db.${table}.findMany({ result.items = await db.${table}.findMany({
select: gen.select, select: gen.select,

View File

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