ship it
This commit is contained in:
parent
3bc6330379
commit
c98e77afa6
|
|
@ -3,7 +3,7 @@ import { useLocal } from "@/utils/use-local";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
|
|
||||||
export type OptionItem = { value: string; label: string; el?: ReactNode };
|
export type OptionItem = { value: any; label: string; el?: ReactNode };
|
||||||
|
|
||||||
export const RawDropdown: FC<{
|
export const RawDropdown: FC<{
|
||||||
options: OptionItem[];
|
options: OptionItem[];
|
||||||
|
|
@ -28,8 +28,18 @@ export const RawDropdown: FC<{
|
||||||
let filtered = options;
|
let filtered = options;
|
||||||
|
|
||||||
if (local.filter) {
|
if (local.filter) {
|
||||||
filtered = options.filter((e) => {
|
filtered = options.filter((e: any) => {
|
||||||
if (e.label.toLowerCase().includes(local.filter)) return true;
|
if (typeof e === "string") {
|
||||||
|
if (e.toLowerCase().includes(local.filter)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof e.label === "string" &&
|
||||||
|
e.label.toLowerCase().includes(local.filter)
|
||||||
|
)
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +128,7 @@ export const RawDropdown: FC<{
|
||||||
{!isEditor && (
|
{!isEditor && (
|
||||||
<input
|
<input
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
value={local.open ? local.input.value : "Halo"}
|
value={local.open ? local.input.value : ""}
|
||||||
className={cx(
|
className={cx(
|
||||||
"c-absolute c-inset-0 c-w-full c-h-full c-outline-none c-p-0",
|
"c-absolute c-inset-0 c-w-full c-h-full c-outline-none c-p-0",
|
||||||
disabled
|
disabled
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,26 @@ export const TypeCustom: FC<{ field: FieldLocal; fm: FMLocal }> = ({
|
||||||
local.custom = field.custom;
|
local.custom = field.custom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!local.exec) {
|
||||||
|
local.exec = true;
|
||||||
|
const callback = (value: any, should_render: boolean) => {
|
||||||
|
local.result = value;
|
||||||
|
if (should_render) {
|
||||||
|
local.render();
|
||||||
|
setTimeout(() => {
|
||||||
|
local.exec = false;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
if (field.custom) {
|
if (field.custom) {
|
||||||
const res = local.custom();
|
const res = local.custom();
|
||||||
if (res instanceof Promise) {
|
if (res instanceof Promise) {
|
||||||
console.error("Custom Function cannot be async");
|
res.then((value) => {
|
||||||
return null;
|
callback(value, true);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
local.result = res;
|
callback(res, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import { FC, useEffect } from "react";
|
||||||
import { FMLocal, FieldLocal } from "../../typings";
|
import { FMLocal, FieldLocal } from "../../typings";
|
||||||
import { OptionItem, RawDropdown } from "../raw/Dropdown";
|
import { OptionItem, RawDropdown } from "../raw/Dropdown";
|
||||||
import { FieldLoading } from "../raw/FieldLoading";
|
import { FieldLoading } from "../raw/FieldLoading";
|
||||||
|
import { sortTree } from "@/comps/list/sort-tree";
|
||||||
|
|
||||||
export type PropTypeRelation = {
|
export type PropTypeRelation = {
|
||||||
type: "has-one" | "has-many";
|
type: "has-one" | "has-many";
|
||||||
on_load: (opt: { value?: any }) => Promise<{ items: any[]; pk: string }>;
|
on_load: (opt: { value?: any }) => Promise<{ items: any[]; pk: string }>;
|
||||||
label: (item: any, pk: string) => string;
|
label: (item: any, pk: string) => string;
|
||||||
|
id_parent: string;
|
||||||
};
|
};
|
||||||
export const FieldTypeRelation: FC<{
|
export const FieldTypeRelation: FC<{
|
||||||
field: FieldLocal;
|
field: FieldLocal;
|
||||||
|
|
@ -35,7 +37,7 @@ export const FieldTypeRelation: FC<{
|
||||||
field.status = "ready";
|
field.status = "ready";
|
||||||
input.render();
|
input.render();
|
||||||
};
|
};
|
||||||
const res = prop.on_load({});
|
const res = prop.on_load({ value });
|
||||||
if (res instanceof Promise) res.then(callback);
|
if (res instanceof Promise) res.then(callback);
|
||||||
else callback(res);
|
else callback(res);
|
||||||
}
|
}
|
||||||
|
|
@ -43,12 +45,33 @@ export const FieldTypeRelation: FC<{
|
||||||
|
|
||||||
let list: OptionItem[] = [];
|
let list: OptionItem[] = [];
|
||||||
if (input.list && input.pk && input.list.length) {
|
if (input.list && input.pk && input.list.length) {
|
||||||
for (const item of input.list) {
|
if (fm.field_def[field.name]?.optional) {
|
||||||
|
list.push({
|
||||||
|
value: null,
|
||||||
|
label: "-",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted = input.list;
|
||||||
|
if (prop.id_parent && input.pk) {
|
||||||
|
sorted = sortTree(sorted, prop.id_parent, input.pk);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of sorted) {
|
||||||
if (typeof item !== "object") continue;
|
if (typeof item !== "object") continue;
|
||||||
let label = "";
|
let label = "";
|
||||||
|
|
||||||
if (typeof prop.label === "function") {
|
if (typeof prop.label === "function") {
|
||||||
label = prop.label(item, input.pk);
|
label = prop.label(item, input.pk);
|
||||||
|
|
||||||
|
if (!label) {
|
||||||
|
const label_arr: string[] = [];
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(item)) {
|
||||||
|
if (k !== input.pk) label_arr.push(v as any);
|
||||||
|
}
|
||||||
|
label = label_arr.join(" ");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const label_arr: string[] = [];
|
const label_arr: string[] = [];
|
||||||
|
|
||||||
|
|
@ -67,7 +90,7 @@ export const FieldTypeRelation: FC<{
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected = null;
|
let selected = null;
|
||||||
if (typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
if (input.pk) selected = value[input.pk];
|
if (input.pk) selected = value[input.pk];
|
||||||
} else {
|
} else {
|
||||||
selected = value;
|
selected = value;
|
||||||
|
|
@ -82,6 +105,11 @@ export const FieldTypeRelation: FC<{
|
||||||
options={list}
|
options={list}
|
||||||
value={selected}
|
value={selected}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
|
if (val === null) {
|
||||||
|
fm.data[field.name] = null;
|
||||||
|
fm.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (input.list && input.pk) {
|
if (input.list && input.pk) {
|
||||||
for (const item of input.list) {
|
for (const item of input.list) {
|
||||||
if (item[input.pk] === val) {
|
if (item[input.pk] === val) {
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ export const formType = (active: { item_id: string }, meta: any) => {
|
||||||
data: typeof ___data;
|
data: typeof ___data;
|
||||||
reload: () => Promise<void>;
|
reload: () => Promise<void>;
|
||||||
submit: () => Promise<void>;
|
submit: () => Promise<void>;
|
||||||
|
render: () => void;
|
||||||
error: {
|
error: {
|
||||||
list: { name: string; error: string }[];
|
list: { name: string; error: string }[];
|
||||||
set: (name: string, error: string) => void;
|
set: (name: string, error: string) => void;
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,11 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
|
||||||
const { on_load, sonar } = fm.props;
|
const { on_load, sonar } = fm.props;
|
||||||
fm.error = formError(fm);
|
fm.error = formError(fm);
|
||||||
|
|
||||||
if (isEditor) {
|
|
||||||
fm.field_def = {};
|
fm.field_def = {};
|
||||||
const defs = parseGenField(fm.props.gen_fields);
|
const defs = parseGenField(fm.props.gen_fields);
|
||||||
for (const d of defs) {
|
for (const d of defs) {
|
||||||
fm.field_def[d.name] = d;
|
fm.field_def[d.name] = d;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fm.reload = () => {
|
fm.reload = () => {
|
||||||
fm.status = isEditor ? "ready" : "loading";
|
fm.status = isEditor ? "ready" : "loading";
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import { createPortal } from "react-dom";
|
||||||
import { Toaster, toast } from "sonner";
|
import { Toaster, toast } from "sonner";
|
||||||
import { getProp } from "../md/utils/get-prop";
|
import { getProp } from "../md/utils/get-prop";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../ui/skeleton";
|
||||||
|
import { sortTree } from "./sort-tree";
|
||||||
|
import { GFCol, parseGenField } from "@/gen/utils";
|
||||||
|
|
||||||
type OnRowClick = (arg: {
|
type OnRowClick = (arg: {
|
||||||
row: any;
|
row: any;
|
||||||
|
|
@ -36,6 +38,7 @@ type TableListProp = {
|
||||||
_meta: Record<string, any>;
|
_meta: Record<string, any>;
|
||||||
gen_fields: string[];
|
gen_fields: string[];
|
||||||
row_click: OnRowClick;
|
row_click: OnRowClick;
|
||||||
|
id_parent?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TableList: FC<TableListProp> = ({
|
export const TableList: FC<TableListProp> = ({
|
||||||
|
|
@ -47,6 +50,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
_meta,
|
_meta,
|
||||||
gen_fields,
|
gen_fields,
|
||||||
row_click,
|
row_click,
|
||||||
|
id_parent,
|
||||||
}) => {
|
}) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
el: null as null | HTMLDivElement,
|
el: null as null | HTMLDivElement,
|
||||||
|
|
@ -58,6 +62,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
if (local.status === "ready") local.status = "resizing";
|
if (local.status === "ready") local.status = "resizing";
|
||||||
local.render();
|
local.render();
|
||||||
}),
|
}),
|
||||||
|
pk: null as null | GFCol,
|
||||||
scrolled: false,
|
scrolled: false,
|
||||||
data: [] as any[],
|
data: [] as any[],
|
||||||
status: "init" as "loading" | "ready" | "resizing" | "reload" | "init",
|
status: "init" as "loading" | "ready" | "resizing" | "reload" | "init",
|
||||||
|
|
@ -91,9 +96,17 @@ export const TableList: FC<TableListProp> = ({
|
||||||
if (fields) {
|
if (fields) {
|
||||||
const rel = fields?.find((e) => e.name === columnKey);
|
const rel = fields?.find((e) => e.name === columnKey);
|
||||||
if (rel && rel.checked) {
|
if (rel && rel.checked) {
|
||||||
|
should_set = false;
|
||||||
|
|
||||||
|
if (rel.type === "has-many") {
|
||||||
|
local.sort.orderBy = {
|
||||||
|
[columnKey]: {
|
||||||
|
_count: direction === "ASC" ? "asc" : "desc",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
const field = rel.checked.find((e) => !e.is_pk);
|
const field = rel.checked.find((e) => !e.is_pk);
|
||||||
if (field) {
|
if (field) {
|
||||||
should_set = false;
|
|
||||||
local.sort.orderBy = {
|
local.sort.orderBy = {
|
||||||
[columnKey]: {
|
[columnKey]: {
|
||||||
[field.name]: direction === "ASC" ? "asc" : "desc",
|
[field.name]: direction === "ASC" ? "asc" : "desc",
|
||||||
|
|
@ -102,6 +115,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (should_set) {
|
if (should_set) {
|
||||||
local.sort.orderBy = {
|
local.sort.orderBy = {
|
||||||
|
|
@ -129,11 +143,14 @@ export const TableList: FC<TableListProp> = ({
|
||||||
local.render();
|
local.render();
|
||||||
|
|
||||||
const orderBy = local.sort.orderBy || undefined;
|
const orderBy = local.sort.orderBy || undefined;
|
||||||
const load_args = {
|
const load_args: any = {
|
||||||
async reload() {},
|
async reload() {},
|
||||||
orderBy,
|
orderBy,
|
||||||
paging: { take: local.paging.take, skip: local.paging.skip },
|
paging: { take: local.paging.take, skip: local.paging.skip },
|
||||||
};
|
};
|
||||||
|
if (id_parent) {
|
||||||
|
load_args.paging = {};
|
||||||
|
}
|
||||||
|
|
||||||
const result = on_load({ ...load_args, mode: "query" });
|
const result = on_load({ ...load_args, mode: "query" });
|
||||||
const callback = (data: any[]) => {
|
const callback = (data: any[]) => {
|
||||||
|
|
@ -190,6 +207,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
col={{
|
col={{
|
||||||
name: props.column.key,
|
name: props.column.key,
|
||||||
value: props.row[props.column.key],
|
value: props.row[props.column.key],
|
||||||
|
depth: props.row.__depth || 0,
|
||||||
}}
|
}}
|
||||||
rows={local.data}
|
rows={local.data}
|
||||||
>
|
>
|
||||||
|
|
@ -233,12 +251,23 @@ 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 (isEditor && local.status !== "ready") {
|
if (isEditor && local.status !== "ready") {
|
||||||
if (local.data.length === 0) {
|
if (local.data.length === 0) {
|
||||||
const load_args = {
|
const load_args: any = {
|
||||||
async reload() {},
|
async reload() {},
|
||||||
paging: { take: local.paging.take, skip: local.paging.skip },
|
paging: { take: local.paging.take, skip: local.paging.skip },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (id_parent) load_args.paging = {};
|
||||||
if (typeof on_load === "function") {
|
if (typeof on_load === "function") {
|
||||||
local.data = on_load({ ...load_args, mode: "query" }) as any;
|
local.data = on_load({ ...load_args, mode: "query" }) as any;
|
||||||
}
|
}
|
||||||
|
|
@ -248,6 +277,11 @@ export const TableList: FC<TableListProp> = ({
|
||||||
|
|
||||||
let selected_idx = -1;
|
let selected_idx = -1;
|
||||||
|
|
||||||
|
let data = local.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
|
||||||
|
|
@ -300,7 +334,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
sortColumns={local.sort.columns}
|
sortColumns={local.sort.columns}
|
||||||
onSortColumnsChange={local.sort.on_change}
|
onSortColumnsChange={local.sort.on_change}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rows={local.data || []}
|
rows={data}
|
||||||
onScroll={local.paging.scroll}
|
onScroll={local.paging.scroll}
|
||||||
renderers={
|
renderers={
|
||||||
local.status !== "ready"
|
local.status !== "ready"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
export const sortTree = (list: any, parent_key: string, pk: string) => {
|
||||||
|
let meta = {} as Record<
|
||||||
|
string,
|
||||||
|
{ item: any; idx: string; depth: number; id_parent: any }
|
||||||
|
>;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
@ -28,12 +28,16 @@ export const MDAction: FC<{ md: MDLocal; PassProp: any; child: any }> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx("c-flex c-flex-row c-space-x-1")}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
"c-flex c-flex-row c-space-x-1 c-items-stretch c-self-stretch c-h-full"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{md.actions.map((e, idx) => {
|
{md.actions.map((e, idx) => {
|
||||||
if (isValidElement(e)) {
|
if (isValidElement(e)) {
|
||||||
return <Fragment key={idx}>{e}</Fragment>;
|
return <Fragment key={idx}>{e}</Fragment>;
|
||||||
}
|
}
|
||||||
if (typeof e === "object" && e.label) {
|
if (typeof e === "object" && (e.action || e.label)) {
|
||||||
return <PassProp item={e}>{child}</PassProp>;
|
return <PassProp item={e}>{child}</PassProp>;
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ export const MDTab: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{md.props.show_head === "only-child" && <MDHeader md={md} mdr={mdr} />}
|
{md.props.show_head === "only-child" && <MDHeader md={md} mdr={mdr} />}
|
||||||
|
|
@ -41,7 +40,7 @@ export const MDNavTab: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"tab-list c-flex c-text-sm",
|
"tab-list c-flex c-text-sm",
|
||||||
mode === "v-tab" && "c-flex-row c-border-b c-pl-2",
|
mode === "v-tab" && "c-flex-row c-border-b c-pl-2 c-pt-2",
|
||||||
mode === "h-tab" && "c-flex-col c-border-r c-pl-2 c-min-w-[100px]"
|
mode === "h-tab" && "c-flex-col c-border-r c-pl-2 c-min-w-[100px]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export const MasterDetail: FC<{
|
||||||
active: "",
|
active: "",
|
||||||
list: [],
|
list: [],
|
||||||
},
|
},
|
||||||
internal: { action_should_refresh: false },
|
internal: { action_should_refresh: true },
|
||||||
childs: {},
|
childs: {},
|
||||||
props: {
|
props: {
|
||||||
mode,
|
mode,
|
||||||
|
|
@ -65,7 +65,7 @@ export const MasterDetail: FC<{
|
||||||
masterDetailApplyParams(md);
|
masterDetailApplyParams(md);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
master: { internal: null, render() {}, pk: null },
|
master: { internal: null, render() {} },
|
||||||
});
|
});
|
||||||
const local = useLocal({ init: false });
|
const local = useLocal({ init: false });
|
||||||
if (isEditor) {
|
if (isEditor) {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export const masterDetailApplyParams = (md: MDLocal) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pk = md.master.pk;
|
const pk = md.pk;
|
||||||
if (pk && row[pk.name]) {
|
if (pk && row[pk.name]) {
|
||||||
md.params.hash[md.name] = row[pk.name];
|
md.params.hash[md.name] = row[pk.name];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export const masterDetailInit = (
|
||||||
md.master.internal = child;
|
md.master.internal = child;
|
||||||
const pk = parseGenField(md.props.gen_fields).find((e) => e.is_pk);
|
const pk = parseGenField(md.props.gen_fields).find((e) => e.is_pk);
|
||||||
if (pk) {
|
if (pk) {
|
||||||
md.master.pk = pk;
|
md.pk = pk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cid === "cb52075a-14ab-455a-9847-6f1d929a2a73") {
|
if (cid === "cb52075a-14ab-455a-9847-6f1d929a2a73") {
|
||||||
|
|
@ -62,7 +62,7 @@ export const masterDetailInit = (
|
||||||
|
|
||||||
export const masterDetailSelected = (md: MDLocal) => {
|
export const masterDetailSelected = (md: MDLocal) => {
|
||||||
md.params.parse();
|
md.params.parse();
|
||||||
const pk = md.master.pk;
|
const pk = md.pk;
|
||||||
if (pk) {
|
if (pk) {
|
||||||
const value = md.params.hash[md.name];
|
const value = md.params.hash[md.name];
|
||||||
if (value) {
|
if (value) {
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@ export type MDLocalInternal = {
|
||||||
list: string[];
|
list: string[];
|
||||||
};
|
};
|
||||||
internal: { action_should_refresh: boolean };
|
internal: { action_should_refresh: boolean };
|
||||||
master: { internal: any; render: () => void; pk: null | GFCol };
|
master: { internal: any; render: () => void };
|
||||||
params: {
|
params: {
|
||||||
hash: any;
|
hash: any;
|
||||||
tabs: any;
|
tabs: any;
|
||||||
parse: () => void;
|
parse: () => void;
|
||||||
apply: () => void;
|
apply: () => void;
|
||||||
};
|
};
|
||||||
|
pk?: GFCol;
|
||||||
props: {
|
props: {
|
||||||
mode: "full" | "h-split" | "v-split";
|
mode: "full" | "h-split" | "v-split";
|
||||||
show_head: "always" | "only-master" | "only-child" | "hidden";
|
show_head: "always" | "only-master" | "only-child" | "hidden";
|
||||||
|
|
@ -75,9 +76,19 @@ export const MasterDetailType = `const md = {
|
||||||
parse: () => void;
|
parse: () => void;
|
||||||
apply: () => void;
|
apply: () => void;
|
||||||
};
|
};
|
||||||
|
props: {
|
||||||
|
mode: "full" | "h-split" | "v-split";
|
||||||
|
show_head: "always" | "only-master" | "only-child" | "hidden";
|
||||||
|
tab_mode: "h-tab" | "v-tab" | "hidden";
|
||||||
|
editor_tab: string;
|
||||||
|
gen_fields: any;
|
||||||
|
gen_table: string;
|
||||||
|
on_init: (md: any) => void;
|
||||||
|
};
|
||||||
internal: { action_should_refresh: boolean };
|
internal: { action_should_refresh: boolean };
|
||||||
render: () => void;
|
render: () => void;
|
||||||
master: { internal: any; render: () => void; pk: null | {
|
master: { internal: any; render: () => void; };
|
||||||
|
pk?: {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
is_pk: boolean;
|
is_pk: boolean;
|
||||||
|
|
@ -87,7 +98,6 @@ export const MasterDetailType = `const md = {
|
||||||
to: { table: string; fields: string[] };
|
to: { table: string; fields: string[] };
|
||||||
fields: GFCol[];
|
fields: GFCol[];
|
||||||
};
|
};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
childs: Record<
|
childs: Record<
|
||||||
string,
|
string,
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ export const gen_form = async (modify: (data: any) => void, data: any) => {
|
||||||
|
|
||||||
result["body"] = data["body"];
|
result["body"] = data["body"];
|
||||||
result.body.content.childs = [];
|
result.body.content.childs = [];
|
||||||
console.log(fields);
|
|
||||||
for (const item of fields.filter((e) => !e.is_pk)) {
|
for (const item of fields.filter((e) => !e.is_pk)) {
|
||||||
result.body.content.childs.push(await newField(item));
|
result.body.content.childs.push(await newField(item));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ async (opt) => {
|
||||||
if (isEditor) return ${JSON.stringify(sample)};
|
if (isEditor) return ${JSON.stringify(sample)};
|
||||||
|
|
||||||
let raw_id = params.id;
|
let raw_id = params.id;
|
||||||
if (typeof md === 'object' && md.selected && md.master?.pk) {
|
if (typeof md === 'object' && md.selected && md.pk) {
|
||||||
const pk = md.master?.pk?.name;
|
const pk = md.pk?.name;
|
||||||
if (md.selected[pk]) {
|
if (md.selected[pk]) {
|
||||||
raw_id = md.selected[pk];
|
raw_id = md.selected[pk];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,11 @@ async ({ form, error }: IForm) => {
|
||||||
const pks = ${JSON.stringify(pks)};
|
const pks = ${JSON.stringify(pks)};
|
||||||
for (const [k, v] of Object.entries(pks)) {
|
for (const [k, v] of Object.entries(pks)) {
|
||||||
if (typeof data[k] === 'object') {
|
if (typeof data[k] === 'object') {
|
||||||
|
if (data[k] === null) {
|
||||||
|
data[k] = {
|
||||||
|
disconnect: true
|
||||||
|
}
|
||||||
|
}
|
||||||
if (data[k][v]) {
|
if (data[k][v]) {
|
||||||
data[k] = {
|
data[k] = {
|
||||||
connect: {
|
connect: {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ import { GFCol, parseGenField } from "../utils";
|
||||||
import { newField } from "./new_field";
|
import { newField } from "./new_field";
|
||||||
import { on_load } from "./on_load";
|
import { on_load } from "./on_load";
|
||||||
|
|
||||||
export const gen_relation = async (modify: (data: any) => void, data: any) => {
|
export const gen_relation = async (
|
||||||
|
modify: (data: any) => void,
|
||||||
|
data: any,
|
||||||
|
arg: { id_parent: string }
|
||||||
|
) => {
|
||||||
const table = JSON.parse(data.gen_table.value);
|
const table = JSON.parse(data.gen_table.value);
|
||||||
const raw_fields = JSON.parse(data.gen_fields.value) as (
|
const raw_fields = JSON.parse(data.gen_fields.value) as (
|
||||||
| string
|
| string
|
||||||
|
|
@ -31,6 +35,10 @@ export const gen_relation = async (modify: (data: any) => void, data: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg.id_parent) {
|
||||||
|
select[arg.id_parent] = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pk) {
|
if (!pk) {
|
||||||
alert("Failed to generate! Primary Key not found. ");
|
alert("Failed to generate! Primary Key not found. ");
|
||||||
return;
|
return;
|
||||||
|
|
@ -40,7 +48,13 @@ export const gen_relation = async (modify: (data: any) => void, data: any) => {
|
||||||
const code = {} as any;
|
const code = {} as any;
|
||||||
if (data["on_load"]) {
|
if (data["on_load"]) {
|
||||||
result["on_load"] = data["on_load"];
|
result["on_load"] = data["on_load"];
|
||||||
result["on_load"].value = on_load({ pk, pks, select, table });
|
result["on_load"].value = on_load({
|
||||||
|
pk,
|
||||||
|
pks,
|
||||||
|
select,
|
||||||
|
table,
|
||||||
|
id_parent: arg.id_parent,
|
||||||
|
});
|
||||||
code.on_load = result["on_load"].value;
|
code.on_load = result["on_load"].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +64,7 @@ export const gen_relation = async (modify: (data: any) => void, data: any) => {
|
||||||
(item:any, pk:string) => {
|
(item:any, pk:string) => {
|
||||||
return \`${Object.entries(select)
|
return \`${Object.entries(select)
|
||||||
.filter(([k, v]) => {
|
.filter(([k, v]) => {
|
||||||
if (typeof v !== "object" && k !== pk?.name) {
|
if (typeof v !== "object" && k !== pk?.name && k !== arg.id_parent) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,22 @@ export type NewFieldArg = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newField = (arg: GFCol) => {
|
export const newField = (arg: GFCol, idx: number) => {
|
||||||
|
let result = `{item["${arg.name}"]}`;
|
||||||
|
let result_built = `render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }), item["${arg.name}"]));`;
|
||||||
|
if (idx === 0) {
|
||||||
|
result = `<FormatValue value={item["${arg.name}"]} tree_depth={item.__depth} />`;
|
||||||
|
result_built = `render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }),
|
||||||
|
React.createElement(FormatValue, { value: item["${arg.name}"], tree_depth: item.__depth })));`;
|
||||||
|
}
|
||||||
return createItem({
|
return createItem({
|
||||||
name: arg.name,
|
name: arg.name,
|
||||||
adv: {
|
adv: {
|
||||||
js: `\
|
js: `\
|
||||||
<div {...props} className={cx(props.className, "")}>
|
<div {...props} className={cx(props.className, "")}>
|
||||||
{item["${arg.name}"]}
|
${result}
|
||||||
</div>`,
|
</div>`,
|
||||||
jsBuilt: `render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }), item["${arg.name}"]));`,
|
jsBuilt: result_built,
|
||||||
},
|
},
|
||||||
dim: {
|
dim: {
|
||||||
h: "full",
|
h: "full",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export const on_load = ({
|
||||||
select,
|
select,
|
||||||
pks,
|
pks,
|
||||||
opt,
|
opt,
|
||||||
|
id_parent,
|
||||||
}: {
|
}: {
|
||||||
pk: GFCol;
|
pk: GFCol;
|
||||||
table: string;
|
table: string;
|
||||||
|
|
@ -15,6 +16,7 @@ export const on_load = ({
|
||||||
before_load: string;
|
before_load: string;
|
||||||
after_load: string;
|
after_load: string;
|
||||||
};
|
};
|
||||||
|
id_parent: string;
|
||||||
}) => {
|
}) => {
|
||||||
const sample: any = {};
|
const sample: any = {};
|
||||||
for (const [k, v] of Object.entries(select) as any) {
|
for (const [k, v] of Object.entries(select) as any) {
|
||||||
|
|
@ -41,11 +43,6 @@ async (opt: { value: any }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let items = await db.${table}.findMany({
|
let items = await db.${table}.findMany({
|
||||||
where: !!id
|
|
||||||
? {
|
|
||||||
${pk.name}: id,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
|
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { codeBuild } from "../master_detail/utils";
|
||||||
export const gen_table_list = async (
|
export const gen_table_list = async (
|
||||||
modify: (data: any) => void,
|
modify: (data: any) => void,
|
||||||
data: any,
|
data: any,
|
||||||
arg: { mode: "table" | "list" | "grid" }
|
arg: { mode: "table" | "list" | "grid"; id_parent: string }
|
||||||
) => {
|
) => {
|
||||||
const table = JSON.parse(data.gen_table.value) as string;
|
const table = JSON.parse(data.gen_table.value) as string;
|
||||||
const raw_fields = JSON.parse(data.gen_fields.value) as (
|
const raw_fields = JSON.parse(data.gen_fields.value) as (
|
||||||
|
|
@ -35,6 +35,10 @@ export const gen_table_list = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg.id_parent) {
|
||||||
|
select[arg.id_parent] = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pk) {
|
if (!pk) {
|
||||||
alert("Failed to generate! Primary Key not found. ");
|
alert("Failed to generate! Primary Key not found. ");
|
||||||
return;
|
return;
|
||||||
|
|
@ -60,11 +64,19 @@ export const gen_table_list = async (
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let first = true;
|
||||||
const child = createItem({
|
const child = createItem({
|
||||||
name: sub_name,
|
name: sub_name,
|
||||||
childs: fields
|
childs: fields
|
||||||
.map((e) => {
|
.map((e) => {
|
||||||
if (e.is_pk) return;
|
if (e.is_pk) 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",
|
||||||
|
|
@ -84,10 +96,10 @@ export const gen_table_list = async (
|
||||||
adv: {
|
adv: {
|
||||||
js: `\
|
js: `\
|
||||||
<div {...props} className={cx(props.className, "")}>
|
<div {...props} className={cx(props.className, "")}>
|
||||||
<FormatValue value={col.value} name={col.name} gen_fields={gen_fields} />
|
<FormatValue value={col.value} name={col.name} gen_fields={gen_fields} ${tree_depth} />
|
||||||
</div>`,
|
</div>`,
|
||||||
jsBuilt: `\
|
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 })));
|
render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }),React.createElement(FormatValue, { value: col.value, name: col.name, gen_fields: gen_fields, ${tree_depth_built} })));
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ type BreadItem = {
|
||||||
actions: `\
|
actions: `\
|
||||||
async () => {
|
async () => {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
action: "delete",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Save",
|
label: "Save",
|
||||||
action: "save",
|
action: "save",
|
||||||
|
|
@ -68,7 +71,9 @@ async ({ submit, reload }: Init) => {
|
||||||
if (tab) {
|
if (tab) {
|
||||||
const actions = await getProp(tab.internal, "actions", { md });
|
const actions = await getProp(tab.internal, "actions", { md });
|
||||||
if (Array.isArray(actions)) {
|
if (Array.isArray(actions)) {
|
||||||
const save_btn = actions.find((e) => e.action === "save");
|
const save_btn = actions
|
||||||
|
.filter((e) => e)
|
||||||
|
.find((e) => e.action === "save");
|
||||||
if (save_btn) {
|
if (save_btn) {
|
||||||
save_btn.onClick = async () => {
|
save_btn.onClick = async () => {
|
||||||
await submit();
|
await submit();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
const cache = {} as Record<string, any>;
|
||||||
|
|
||||||
export const gen_prop_fields = async (gen_table: string) => {
|
export const gen_prop_fields = async (gen_table: string) => {
|
||||||
|
if (cache[gen_table]) return cache[gen_table];
|
||||||
|
|
||||||
const result: {
|
const result: {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -48,5 +52,10 @@ export const gen_prop_fields = async (gen_table: string) => {
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cache[gen_table]) {
|
||||||
|
cache[gen_table] = result;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
const cache: any = [];
|
||||||
|
|
||||||
export const gen_props_table = async () => {
|
export const gen_props_table = async () => {
|
||||||
|
if (cache.length > 0) return cache;
|
||||||
|
|
||||||
const result = [{ value: "", label: "" }];
|
const result = [{ value: "", label: "" }];
|
||||||
return [
|
const final = [
|
||||||
...result,
|
...result,
|
||||||
...(await db._schema.tables()).map((e) => ({
|
...(await db._schema.tables()).map((e) => ({
|
||||||
value: e,
|
value: e,
|
||||||
|
|
@ -8,4 +12,10 @@ export const gen_props_table = async () => {
|
||||||
reload: ["gen_fields", "gen_label"],
|
reload: ["gen_fields", "gen_label"],
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
for (const f of final) {
|
||||||
|
cache.push(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ export const FormatValue: FC<{
|
||||||
value: any;
|
value: any;
|
||||||
name: string;
|
name: string;
|
||||||
gen_fields: string[];
|
gen_fields: string[];
|
||||||
|
tree_depth?: number;
|
||||||
}> = (prop) => {
|
}> = (prop) => {
|
||||||
const { value, gen_fields, name } = prop;
|
const { value, gen_fields, name, tree_depth } = prop;
|
||||||
|
|
||||||
|
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)) {
|
||||||
fields_map.set(
|
fields_map.set(
|
||||||
|
|
@ -49,5 +51,32 @@ export const FormatValue: FC<{
|
||||||
|
|
||||||
return JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
}
|
}
|
||||||
return <>{value}</>;
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue