This commit is contained in:
rizky 2024-03-25 22:38:27 -07:00
parent 4559a90ca1
commit 9957e9d626
7 changed files with 146 additions and 35 deletions

View File

@ -93,7 +93,6 @@ export const Field: FC<{
}, },
modify: null as any, modify: null as any,
}); });
const textAreaRef = useRef<any>(); const textAreaRef = useRef<any>();
useEffect(() => { useEffect(() => {
autosize(textAreaRef.current); autosize(textAreaRef.current);

View File

@ -1,30 +1,54 @@
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 { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
import { import {
MasterDetailConfig, MasterDetailConfig,
MasterDetailLocal, MasterDetailLocal,
MasterDetailProp, MasterDetailProp,
} from "./type"; } from "./type";
import { Tab } from "../custom/Tab"; import { GFCol } from "@/gen/utils";
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; import { master_detail_gen_hash, master_detail_params } from "./utils";
export const MasterDetail: FC<MasterDetailProp> = (props) => { export const MasterDetail: FC<MasterDetailProp> = (props) => {
const { header, PassProp, master, detail, mode, title, actions } = props; const { header, name, mode, title, actions, gen_fields } = props;
const md = useLocal<MasterDetailLocal & { cache_internal: any }>({ const md = useLocal<MasterDetailLocal & { cache_internal: any }>(
mode, {
selected: null, name,
active_tab: "", mode,
ui: { selected: null,
back: false, active_tab: "",
title: title, ui: {
breadcrumb: [], back: false,
default_actions: null as any, title: title,
actions: null as any, breadcrumb: [],
default_actions: null as any,
actions: null as any,
},
cache_internal: {},
cache: null as any,
pk: null as null | GFCol,
}, },
cache_internal: {}, () => {
cache: null as any, if (!isEditor) {
}); const hash = master_detail_params(md);
if (hash && hash[name] && md.pk) {
if (md.pk.type === "int") {
md.selected = { [md.pk.name]: parseInt(hash[name]) };
} else {
md.selected = { [md.pk.name]: hash[name] };
}
md.render();
}
}
}
);
if (!md.pk && gen_fields) {
for (const str of gen_fields) {
const f = JSON.parse(str) as GFCol;
if (f.is_pk) md.pk = f;
}
}
if (!md.ui.actions) { if (!md.ui.actions) {
md.ui.actions = actions(md); md.ui.actions = actions(md);
@ -72,6 +96,24 @@ const BreadcrumbMode: FC<{
props: MasterDetailProp; props: MasterDetailProp;
md: MasterDetailConfig; md: MasterDetailConfig;
}> = ({ props, md }) => { }> = ({ props, md }) => {
const local = useLocal({ init: false }, () => {
local.init = true;
local.render();
});
if (local.init) {
const hash = master_detail_params(md);
delete hash.parent_id;
if (!md.selected) {
delete hash[md.name];
location.hash = master_detail_gen_hash(hash);
} else if (md.pk) {
hash[md.name] = md.selected[md.pk.name];
location.hash = master_detail_gen_hash(hash);
}
}
return ( return (
<div className={cx("c-flex-1 c-flex-col c-flex")}> <div className={cx("c-flex-1 c-flex-col c-flex")}>
<div <div
@ -80,6 +122,11 @@ const BreadcrumbMode: FC<{
<props.PassProp md={md}>{props.master}</props.PassProp> <props.PassProp md={md}>{props.master}</props.PassProp>
</div> </div>
{md.selected && <Detail props={props} md={md} />} {md.selected && <Detail props={props} md={md} />}
{isEditor && !local.init && (
<div className="c-hidden">
<Detail props={props} md={md} />
</div>
)}
</div> </div>
); );
}; };

View File

@ -1,3 +1,4 @@
import { GFCol } from "@/gen/utils";
import { ReactElement, ReactNode } from "react"; import { ReactElement, ReactNode } from "react";
export type MasterDetailProp = { export type MasterDetailProp = {
@ -8,6 +9,8 @@ export type MasterDetailProp = {
mode: "breadcrumb" | "vertical" | "horizontal"; mode: "breadcrumb" | "vertical" | "horizontal";
title: string; title: string;
actions: (md: any) => MasterDetailAction[]; actions: (md: any) => MasterDetailAction[];
gen_fields: any;
name: string;
}; };
type MasterDetailAction = { type MasterDetailAction = {
@ -18,6 +21,7 @@ type MasterDetailAction = {
}; };
export type MasterDetailLocal = { export type MasterDetailLocal = {
name: string;
mode: MasterDetailProp["mode"]; mode: MasterDetailProp["mode"];
selected: null | Record<string, any>; selected: null | Record<string, any>;
active_tab: string; active_tab: string;
@ -29,6 +33,7 @@ export type MasterDetailLocal = {
actions: MasterDetailAction[]; actions: MasterDetailAction[];
}; };
cache: (name: string, opt?: { reset: boolean }) => any; cache: (name: string, opt?: { reset: boolean }) => any;
pk: null | GFCol;
}; };
export type MasterDetailConfig = MasterDetailLocal & { render: () => void }; export type MasterDetailConfig = MasterDetailLocal & { render: () => void };
@ -54,6 +59,7 @@ export const master_detail_typings = {
actions: ${action_type}[]; actions: ${action_type}[];
}; };
cache: (name: string, opt?: { reset: boolean }) => any; cache: (name: string, opt?: { reset: boolean }) => any;
pk: null | any
}`, }`,
action_type, action_type,
}; };

33
comps/mst/utils.ts Executable file
View File

@ -0,0 +1,33 @@
import { MasterDetailConfig } from "./type";
export const master_detail_params = (md: MasterDetailConfig) => {
let parent_id =
md.pk?.type === "int"
? parseInt(md.selected?.[md.pk?.name || ""])
: md.selected?.[md.pk?.name || ""];
const hash: any = {};
for (const h of location.hash.split("#")) {
if (h) {
const [tab_name, tab_val] = h.split("=");
if (tab_name && tab_val) {
hash[tab_name] = tab_val;
}
}
}
if (parent_id) {
return { ...hash, parent_id };
}
return hash;
};
export const master_detail_gen_hash = (
obj: Record<string, number | string>
) => {
let hash = "";
for (const [k, v] of Object.entries(obj)) {
hash += `#${k}=${v}`;
}
return hash;
};

View File

@ -20,8 +20,13 @@ export const on_load = ({
async (opt) => { async (opt) => {
if (isEditor) return {}; if (isEditor) return {};
${
opt?.before_load
? opt.before_load
: `
let id = ${pk.type === "int" ? "parseInt(params.id)" : "params.id"}; let id = ${pk.type === "int" ? "parseInt(params.id)" : "params.id"};
${opt?.before_load} `
}
let item = {}; let item = {};
if (id){ if (id){
@ -32,16 +37,19 @@ async (opt) => {
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")}, select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
}); });
for (const [k, v] of Object.entries(item)) { if (item){
${Object.entries(pks) for (const [k, v] of Object.entries(item)) {
.map(([k, v]) => { ${Object.entries(pks)
return `\ .map(([k, v]) => {
if (k === "${k}") { return `\
if (v?.["${v}"]) item[k] = { connect: { ${v}: v?.["${v}"] } } as any; if (k === "${k}") {
else delete item[k]; if (v?.["${v}"]) item[k] = { connect: { ${v}: v?.["${v}"] } } as any;
}`; else delete item[k];
}) }`;
.join("\n")} })
.join("\n")}
}
}
${opt?.after_load} ${opt?.after_load}

View File

@ -1,10 +1,15 @@
import { GFCol } from "../utils";
export const form_before_load = ( export const form_before_load = (
table: string, table: string,
pk: string, pk: GFCol,
title: string, title: string,
label: string label: string
) => { ) => {
return ` return `
const id = master_detail_params(md).parent_id;
const after_load = (item: any) => { const after_load = (item: any) => {
const set_actions = () => const set_actions = () =>
(md.ui.actions = [ (md.ui.actions = [
@ -16,7 +21,9 @@ export const form_before_load = (
md.ui.actions = [{ label: "Deleting...", type: "ghost" }]; md.ui.actions = [{ label: "Deleting...", type: "ghost" }];
md.render(); md.render();
await db.${table}.delete({ where: { ${pk}: item.${pk} } }); await db.${table}.delete({ where: { ${pk.name}: item.${
pk.name
} } });
setTimeout(() => { setTimeout(() => {
md.ui.actions = [...md.ui.default_actions]; md.ui.actions = [...md.ui.default_actions];
@ -44,10 +51,12 @@ export const form_before_load = (
}, },
]); ]);
set_actions(); set_actions();
md.ui.breadcrumb = [["${title}", ""]${label ? `, item?.["${label}"]` : ""}]; md.ui.breadcrumb = [[md.ui.title, ""]${
label ? `, item?.["${label}"]` : ""
}];
md.render(); md.render();
}; };
md.ui.breadcrumb = [["${title}", ""], "..."]; md.ui.breadcrumb = [[md.ui.title, ""], "..."];
md.render(); md.render();
`; `;

View File

@ -11,6 +11,7 @@ import { gen_columns } from "../gen_table/columns";
import { newField as table_new_field } from "../gen_table/new_field"; import { newField as table_new_field } from "../gen_table/new_field";
import { gen_detail } from "./gen_detail"; import { gen_detail } from "./gen_detail";
import { form_before_load } from "./form_before_load"; import { form_before_load } from "./form_before_load";
import { createId } from "@paralleldrive/cuid2";
export const gen_md = (modify: (data: any) => void, data: any) => { export const gen_md = (modify: (data: any) => void, data: any) => {
const table = JSON.parse(data.gen_table.value); const table = JSON.parse(data.gen_table.value);
@ -86,7 +87,7 @@ export const gen_md = (modify: (data: any) => void, data: any) => {
const detail = gen_detail(); const detail = gen_detail();
const title = parse(get(data, "title.value")); const title = parse(get(data, "title.value"));
const label = parse(get(data, "gen_label.value")); const label = parse(get(data, "gen_label.value"));
const before_load = form_before_load(table, pk.name, title, label); const before_load = form_before_load(table, pk, title, label);
detail.props["on_load"].value = form_on_load({ detail.props["on_load"].value = form_on_load({
pk, pk,
@ -105,9 +106,17 @@ export const gen_md = (modify: (data: any) => void, data: any) => {
const childs = get(detail.props, "body.content.childs"); const childs = get(detail.props, "body.content.childs");
if (Array.isArray(childs)) { if (Array.isArray(childs)) {
detail.props.body.content.childs = new_fields.map(form_new_field) as any; detail.props.body.content.childs = new_fields.map(form_new_field) as any;
console.log(detail.props.body.content.childs);
} }
result["detail"].content = detail.content; result["detail"].content = {
id: createId(),
name: "Detail",
type: "item",
dim: {
w: "full",
h: "full",
},
childs: [detail.content],
};
} }
modify(result); modify(result);