fix
This commit is contained in:
parent
ebc3e19935
commit
ac74e0770b
|
|
@ -1,7 +1,5 @@
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import { FC, ReactNode, useEffect } from "react";
|
import { FC, ReactNode, useEffect } from "react";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
|
||||||
import get from "lodash.get";
|
|
||||||
import { FieldLoading } from "../ui/field-loading";
|
import { FieldLoading } from "../ui/field-loading";
|
||||||
|
|
||||||
export type BreadItem = {
|
export type BreadItem = {
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ export const BaseField = (prop: {
|
||||||
typeof field.prefix === "function"
|
typeof field.prefix === "function"
|
||||||
? field.prefix()
|
? field.prefix()
|
||||||
: typeof field.prefix === "string"
|
: typeof field.prefix === "string"
|
||||||
? field.prefix
|
? field.prefix
|
||||||
: null;
|
: null;
|
||||||
const suffix =
|
const suffix =
|
||||||
typeof field.suffix === "function"
|
typeof field.suffix === "function"
|
||||||
? field.suffix()
|
? field.suffix()
|
||||||
: typeof field.suffix === "string"
|
: typeof field.suffix === "string"
|
||||||
? field.prefix
|
? field.prefix
|
||||||
: null;
|
: null;
|
||||||
const name = field.name;
|
const name = field.name;
|
||||||
const errors = fm.error.get(name);
|
const errors = fm.error.get(name);
|
||||||
|
|
||||||
|
|
@ -47,9 +47,7 @@ export const BaseField = (prop: {
|
||||||
w === "½" && "c-w-1/2",
|
w === "½" && "c-w-1/2",
|
||||||
w === "⅓" && "c-w-1/3",
|
w === "⅓" && "c-w-1/3",
|
||||||
w === "¼" && "c-w-1/4",
|
w === "¼" && "c-w-1/4",
|
||||||
field.type === "link"
|
"c-flex-col c-space-y-1",
|
||||||
? "c-flex-row c-items-stretch c-min-h-[78px]"
|
|
||||||
: "c-flex-col c-space-y-1",
|
|
||||||
field.focused && "focused",
|
field.focused && "focused",
|
||||||
field.disabled && "disabled",
|
field.disabled && "disabled",
|
||||||
typeof fm.data[name] !== "undefined" &&
|
typeof fm.data[name] !== "undefined" &&
|
||||||
|
|
@ -65,9 +63,7 @@ export const BaseField = (prop: {
|
||||||
!["toogle", "button", "radio", "checkbox"].includes(arg.sub_type)
|
!["toogle", "button", "radio", "checkbox"].includes(arg.sub_type)
|
||||||
? cx(
|
? cx(
|
||||||
"field-outer c-overflow-hidden c-flex-1 c-flex c-flex-row c-text-sm c-bg-white",
|
"field-outer c-overflow-hidden c-flex-1 c-flex c-flex-row c-text-sm c-bg-white",
|
||||||
field.type === "link"
|
"c-border c-rounded "
|
||||||
? " c-items-center"
|
|
||||||
: "c-border c-rounded "
|
|
||||||
)
|
)
|
||||||
: "",
|
: "",
|
||||||
fm.status === "loading"
|
fm.status === "loading"
|
||||||
|
|
@ -75,13 +71,13 @@ export const BaseField = (prop: {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
`
|
`
|
||||||
: field.disabled
|
: field.disabled
|
||||||
? "c-border-gray-100"
|
? "c-border-gray-100"
|
||||||
: errors.length > 0
|
: errors.length > 0
|
||||||
? field.focused
|
? field.focused
|
||||||
? "c-border-red-600 c-bg-red-50 c-outline c-outline-red-700"
|
? "c-border-red-600 c-bg-red-50 c-outline c-outline-red-700"
|
||||||
: "c-border-red-600 c-bg-red-50"
|
: "c-border-red-600 c-bg-red-50"
|
||||||
: field.focused &&
|
: field.focused &&
|
||||||
"c-border-blue-700 c-outline c-outline-blue-700",
|
"c-border-blue-700 c-outline c-outline-blue-700",
|
||||||
css`
|
css`
|
||||||
& > .field-inner {
|
& > .field-inner {
|
||||||
min-height: 35px;
|
min-height: 35px;
|
||||||
|
|
@ -105,7 +101,6 @@ export const BaseField = (prop: {
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"field-inner c-flex-1 c-flex c-items-center",
|
"field-inner c-flex-1 c-flex c-items-center",
|
||||||
field.type === "link" && "c-justify-end",
|
|
||||||
field.focused && "focused",
|
field.focused && "focused",
|
||||||
field.disabled && "c-pointer-events-none"
|
field.disabled && "c-pointer-events-none"
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,7 @@ export const Field: FC<FieldProp> = (arg) => {
|
||||||
w === "½" && "c-w-1/2",
|
w === "½" && "c-w-1/2",
|
||||||
w === "⅓" && "c-w-1/3",
|
w === "⅓" && "c-w-1/3",
|
||||||
w === "¼" && "c-w-1/4",
|
w === "¼" && "c-w-1/4",
|
||||||
field.type === "link"
|
"c-flex-col c-space-y-1"
|
||||||
? "c-flex-row c-items-stretch c-min-h-[78px]"
|
|
||||||
: "c-flex-col c-space-y-1"
|
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
ref={typeof arg.field_ref === "function" ? arg.field_ref : undefined}
|
ref={typeof arg.field_ref === "function" ? arg.field_ref : undefined}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { TableEdit } from "./table-edit/TableEdit";
|
||||||
import { FieldTypeInput, PropTypeInput } from "./type/TypeInput";
|
import { FieldTypeInput, PropTypeInput } from "./type/TypeInput";
|
||||||
import { MultiOption } from "./type/TypeMultiOption";
|
import { MultiOption } from "./type/TypeMultiOption";
|
||||||
import { SingleOption } from "./type/TypeSingleOption";
|
import { SingleOption } from "./type/TypeSingleOption";
|
||||||
|
import { FieldLink } from "./type/TypeLink";
|
||||||
|
|
||||||
const modify = {
|
const modify = {
|
||||||
timeout: null as any,
|
timeout: null as any,
|
||||||
|
|
@ -108,7 +109,7 @@ export const FieldInput: FC<{
|
||||||
!["toogle", "button", "radio", "checkbox"].includes(arg.sub_type)
|
!["toogle", "button", "radio", "checkbox"].includes(arg.sub_type)
|
||||||
? cx(
|
? cx(
|
||||||
"field-outer c-overflow-hidden c-flex-1 c-flex c-flex-row c-text-sm c-bg-white",
|
"field-outer c-overflow-hidden c-flex-1 c-flex c-flex-row c-text-sm c-bg-white",
|
||||||
field.type === "link" ? " c-items-center" : "c-border c-rounded "
|
"c-border c-rounded "
|
||||||
)
|
)
|
||||||
: "",
|
: "",
|
||||||
fm.status === "loading"
|
fm.status === "loading"
|
||||||
|
|
@ -145,7 +146,6 @@ export const FieldInput: FC<{
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"field-inner c-flex-1 c-flex c-items-center",
|
"field-inner c-flex-1 c-flex c-items-center",
|
||||||
field.type === "link" && "c-justify-end",
|
|
||||||
field.focused && "focused",
|
field.focused && "focused",
|
||||||
disabled && "c-pointer-events-none c-bg-gray-50"
|
disabled && "c-pointer-events-none c-bg-gray-50"
|
||||||
)}
|
)}
|
||||||
|
|
@ -158,7 +158,9 @@ export const FieldInput: FC<{
|
||||||
<>{custom}</>
|
<>{custom}</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{type_field === "link" && <>ini link</>}
|
{type_field === "link" && (
|
||||||
|
<FieldLink field={field} fm={fm} arg={arg} />
|
||||||
|
)}
|
||||||
{["date", "input"].includes(type_field) ? (
|
{["date", "input"].includes(type_field) ? (
|
||||||
<FieldTypeInput
|
<FieldTypeInput
|
||||||
field={field}
|
field={field}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,7 @@ export const Label: FC<{ field: FieldLocal; fm: FMLocal }> = ({
|
||||||
const errors = fm.error.get(field.name);
|
const errors = fm.error.get(field.name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={cx("label c-text-sm c-flex c-items-center", "c-mt-3")}>
|
||||||
className={cx(
|
|
||||||
"label c-text-sm c-flex c-items-center",
|
|
||||||
field.type === "link" ? "" : "c-mt-3"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className={cx(errors.length > 0 && `c-text-red-600`)}>
|
<span className={cx(errors.length > 0 && `c-text-red-600`)}>
|
||||||
{field.label}
|
{field.label}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
import { FieldLoading, Spinner } from "lib/comps/ui/field-loading";
|
||||||
|
import { hashSum } from "lib/utils/hash-sum";
|
||||||
|
import { getPathname } from "lib/utils/pathname";
|
||||||
|
import { useLocal } from "lib/utils/use-local";
|
||||||
|
import { ArrowUpRight } from "lucide-react";
|
||||||
|
import { FC, ReactNode, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
|
||||||
|
export const FieldLink: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
const local = useLocal({
|
||||||
|
text: "",
|
||||||
|
init: false,
|
||||||
|
navigating: false,
|
||||||
|
custom: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Link = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: (arg: { icon: any }) => ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"c-flex-1 c-my-1 c-px-2 c-rounded-md c-flex c-items-center cursor-pointer c-space-x-1 c-transition-all",
|
||||||
|
css`
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
&:hover {
|
||||||
|
background-color: #dcedfc;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!isEditor) {
|
||||||
|
local.navigating = true;
|
||||||
|
local.render();
|
||||||
|
if (!(await navigateLink(arg.link, field))) {
|
||||||
|
local.navigating = false;
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children({
|
||||||
|
icon: local.navigating ? (
|
||||||
|
<Spinner
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
padding-left: 4px;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ArrowUpRight size={15} />
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (arg.link && !local.init) {
|
||||||
|
if (typeof arg.link.text === "string") {
|
||||||
|
local.text = arg.link.text;
|
||||||
|
local.init = true;
|
||||||
|
} else if (typeof arg.link.text === "function") {
|
||||||
|
const res = arg.link.text({
|
||||||
|
field,
|
||||||
|
Link,
|
||||||
|
});
|
||||||
|
if (res instanceof Promise) {
|
||||||
|
res.then((text) => {
|
||||||
|
local.text = text;
|
||||||
|
local.init = true;
|
||||||
|
local.render();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
local.text = res;
|
||||||
|
local.init = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx("c-px-2")}>
|
||||||
|
{!local.init ? (
|
||||||
|
<FieldLoading height="short" />
|
||||||
|
) : (
|
||||||
|
<Link>
|
||||||
|
{({ icon }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>{local.text}</div>
|
||||||
|
{icon}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LinkParam = {
|
||||||
|
url: string;
|
||||||
|
where: any;
|
||||||
|
hash: any;
|
||||||
|
prefix: {
|
||||||
|
label: any;
|
||||||
|
url: string;
|
||||||
|
md?: { name: string; value: any };
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigateLink = async (link: FieldProp["link"], field: FieldLocal) => {
|
||||||
|
let params = link.params(field);
|
||||||
|
|
||||||
|
if (typeof params === "object") {
|
||||||
|
if (params instanceof Promise) {
|
||||||
|
params = await params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const md = params.md;
|
||||||
|
const where = params.where;
|
||||||
|
|
||||||
|
const prefix: LinkParam["prefix"] = [];
|
||||||
|
|
||||||
|
if (md) {
|
||||||
|
md.header.render();
|
||||||
|
if (md.header.breadcrumb.length > 0) {
|
||||||
|
const path = getPathname({ hash: false });
|
||||||
|
let i = 0;
|
||||||
|
for (const b of md.header.breadcrumb) {
|
||||||
|
prefix.push({
|
||||||
|
label: b.label,
|
||||||
|
url: `${path}`,
|
||||||
|
md:
|
||||||
|
i > 0
|
||||||
|
? { name: md.name, value: md.selected[md.pk?.name || ""] }
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const values: LinkParam = {
|
||||||
|
url: getPathname({ hash: false }),
|
||||||
|
where,
|
||||||
|
prefix,
|
||||||
|
hash: "",
|
||||||
|
};
|
||||||
|
const vhash = hashSum(values);
|
||||||
|
values.hash = vhash;
|
||||||
|
|
||||||
|
if (!link.url) {
|
||||||
|
alert("No URL defined!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await api._kv("set", vhash, values);
|
||||||
|
const lnk = location.hash.split("#").find((e) => e.startsWith("lnk="));
|
||||||
|
let prev_link = "";
|
||||||
|
if (lnk) {
|
||||||
|
prev_link = lnk.split("=").pop() || "";
|
||||||
|
if (prev_link) prev_link = prev_link + "+";
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(`${link.url}#lnk=${prev_link + vhash}`);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseLink = () => {
|
||||||
|
const lnk = location.hash.split("#").find((e) => e.startsWith("lnk="));
|
||||||
|
|
||||||
|
if (lnk) {
|
||||||
|
const res = lnk.split("=").pop() || "";
|
||||||
|
if (res) {
|
||||||
|
return res.split("+");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
export const fetchLinkParams = async (
|
||||||
|
parsed_link?: ReturnType<typeof parseLink>
|
||||||
|
) => {
|
||||||
|
const parsed = parsed_link || parseLink();
|
||||||
|
|
||||||
|
return await Promise.all(parsed.map((e) => api._kv("get", e)));
|
||||||
|
};
|
||||||
|
|
@ -60,11 +60,7 @@ export const generateForm = async (
|
||||||
after_load: `
|
after_load: `
|
||||||
if (typeof md === "object") {
|
if (typeof md === "object") {
|
||||||
opt.fm.status = "ready";
|
opt.fm.status = "ready";
|
||||||
if (item) {
|
md.selected = opt.fm.data;
|
||||||
for (const [k,v] of Object.entries(item)) {
|
|
||||||
md.selected[k] = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
md.header.render();
|
md.header.render();
|
||||||
md.render();
|
md.render();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { GFCol } from "@/gen/utils";
|
import { GFCol } from "@/gen/utils";
|
||||||
import { MutableRefObject, ReactElement, ReactNode } from "react";
|
import { FC, MutableRefObject, ReactElement, ReactNode } from "react";
|
||||||
import { editorFormData } from "./utils/ed-data";
|
import { editorFormData } from "./utils/ed-data";
|
||||||
|
import { MDLocal } from "../md/utils/typings";
|
||||||
|
|
||||||
export type FMProps = {
|
export type FMProps = {
|
||||||
on_init: (arg: { fm: FMLocal; submit: any; reload: any }) => any;
|
on_init: (arg: { fm: FMLocal; submit: any; reload: any }) => any;
|
||||||
|
|
@ -46,6 +47,18 @@ export type FieldProp = {
|
||||||
label: string;
|
label: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
props?: any;
|
props?: any;
|
||||||
|
link: {
|
||||||
|
text:
|
||||||
|
| string
|
||||||
|
| ((arg: {
|
||||||
|
field: FieldLocal;
|
||||||
|
Link: FC<{ children: any; }>;
|
||||||
|
}) => Promise<string> | string);
|
||||||
|
url: string;
|
||||||
|
params: (
|
||||||
|
field: FieldLocal
|
||||||
|
) => { md: MDLocal; where: any } | Promise<{ md: MDLocal; where: any }>;
|
||||||
|
};
|
||||||
fm: FMLocal;
|
fm: FMLocal;
|
||||||
type: FieldType | (() => FieldType);
|
type: FieldType | (() => FieldType);
|
||||||
required: ("y" | "n") | (() => "y" | "n");
|
required: ("y" | "n") | (() => "y" | "n");
|
||||||
|
|
@ -142,7 +155,7 @@ export type FMInternal = {
|
||||||
soft_delete: {
|
soft_delete: {
|
||||||
field: any;
|
field: any;
|
||||||
};
|
};
|
||||||
has_fields_container: boolean,
|
has_fields_container: boolean;
|
||||||
};
|
};
|
||||||
export type FMLocal = FMInternal & { render: () => void };
|
export type FMLocal = FMInternal & { render: () => void };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
} from "./utils/md-hash";
|
} from "./utils/md-hash";
|
||||||
import { mdRenderLoop } from "./utils/md-render-loop";
|
import { mdRenderLoop } from "./utils/md-render-loop";
|
||||||
import { MDLocalInternal, MDProps } from "./utils/typings";
|
import { MDLocalInternal, MDProps } from "./utils/typings";
|
||||||
import { any } from "zod";
|
|
||||||
|
|
||||||
export const MasterDetail: FC<MDProps> = (arg) => {
|
export const MasterDetail: FC<MDProps> = (arg) => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -36,8 +35,8 @@ export const MasterDetail: FC<MDProps> = (arg) => {
|
||||||
status: isEditor ? "init" : "ready",
|
status: isEditor ? "init" : "ready",
|
||||||
actions: [],
|
actions: [],
|
||||||
header: {
|
header: {
|
||||||
|
loading: false,
|
||||||
breadcrumb: [],
|
breadcrumb: [],
|
||||||
internalRender() {},
|
|
||||||
render: () => {},
|
render: () => {},
|
||||||
master: { prefix: null, suffix: null },
|
master: { prefix: null, suffix: null },
|
||||||
child: { prefix: null, suffix: null },
|
child: { prefix: null, suffix: null },
|
||||||
|
|
@ -60,6 +59,7 @@ export const MasterDetail: FC<MDProps> = (arg) => {
|
||||||
item: _item,
|
item: _item,
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
|
links: [],
|
||||||
hash: {},
|
hash: {},
|
||||||
tabs: {},
|
tabs: {},
|
||||||
parse: () => {
|
parse: () => {
|
||||||
|
|
@ -93,7 +93,9 @@ export const MasterDetail: FC<MDProps> = (arg) => {
|
||||||
if (pk) {
|
if (pk) {
|
||||||
const value = md.params.hash[md.name];
|
const value = md.params.hash[md.name];
|
||||||
if (value) {
|
if (value) {
|
||||||
md.selected = { [pk.name]: value };
|
if (!md.selected) {
|
||||||
|
md.selected = { [pk.name]: value };
|
||||||
|
}
|
||||||
const tab = md.params.tabs[md.name];
|
const tab = md.params.tabs[md.name];
|
||||||
if (tab && md.tab.list.includes(tab)) {
|
if (tab && md.tab.list.includes(tab)) {
|
||||||
md.tab.active = tab;
|
md.tab.active = tab;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ export const generateMDForm = async (
|
||||||
if (Object.keys(md.selected).length === 0){
|
if (Object.keys(md.selected).length === 0){
|
||||||
breads.push({ label: "Add New" });
|
breads.push({ label: "Add New" });
|
||||||
} else {
|
} else {
|
||||||
breads.push({ label: "Edit" });
|
breads.push({ label: guessLabel(md.selected) || "Detail" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { GenFn } from "lib/gen/utils";
|
import { GenFn } from "lib/gen/utils";
|
||||||
import { generateMDForm } from "./md-form";
|
import { generateMDForm } from "./md-form";
|
||||||
import { generateMDList } from "./md-list";
|
import { generateMDList } from "./md-list";
|
||||||
|
import capitalize from "lodash.capitalize";
|
||||||
const w = window as any;
|
const w = window as any;
|
||||||
export const generateMasterDetail: GenFn<{
|
export const generateMasterDetail: GenFn<{
|
||||||
item: PrasiItem;
|
item: PrasiItem;
|
||||||
|
|
@ -16,7 +16,9 @@ export const generateMasterDetail: GenFn<{
|
||||||
);
|
);
|
||||||
const title = fn_title();
|
const title = fn_title();
|
||||||
if (!title && item.edit.props?.gen_table) {
|
if (!title && item.edit.props?.gen_table) {
|
||||||
item.edit.setProp('title', item.edit.props?.gen_table)
|
const table = { ...item.edit.props?.gen_table };
|
||||||
|
table.value = `${capitalize(table.value as string)}`;
|
||||||
|
item.edit.setProp("title", table);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { useLocal } from "lib/utils/use-local";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
|
import { breadcrumbPrefix } from "../utils/md-hash";
|
||||||
import { MDLocal, MDRef } from "../utils/typings";
|
import { MDLocal, MDRef } from "../utils/typings";
|
||||||
import { MDHeader } from "./MDHeader";
|
import { MDHeader } from "./MDHeader";
|
||||||
import { useLocal } from "lib/utils/use-local";
|
|
||||||
|
|
||||||
export const should_show_tab = (md: MDLocal) => {
|
export const should_show_tab = (md: MDLocal) => {
|
||||||
if (isEditor) {
|
if (isEditor) {
|
||||||
|
|
@ -114,19 +115,8 @@ export const MDRenderTab: FC<{
|
||||||
on_init: () => MDLocal;
|
on_init: () => MDLocal;
|
||||||
breadcrumb: () => Array<any>;
|
breadcrumb: () => Array<any>;
|
||||||
}> = ({ child, on_init, breadcrumb }) => {
|
}> = ({ child, on_init, breadcrumb }) => {
|
||||||
const local = useLocal({ md: null as null | MDLocal });
|
const md = on_init();
|
||||||
if (!local.md) {
|
md.header.child.breadcrumb = breadcrumb;
|
||||||
local.md = on_init();
|
|
||||||
}
|
|
||||||
const md = local.md;
|
|
||||||
|
|
||||||
md.header.render = () => {
|
|
||||||
md.header.breadcrumb = breadcrumb();
|
|
||||||
md.header.internalRender();
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
md.header.render();
|
|
||||||
}, Object.values(md.deps || {}) || []);
|
|
||||||
|
|
||||||
return <>{child}</>;
|
return <>{child}</>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
|
import { breadcrumbPrefix } from "../utils/md-hash";
|
||||||
import { MDLocal, MDRef } from "../utils/typings";
|
import { MDLocal, MDRef } from "../utils/typings";
|
||||||
|
|
||||||
export const MDHeader: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
export const MDHeader: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
const [_, set] = useState({});
|
const [_, set] = useState({});
|
||||||
const head = mdr.item.edit.props?.header.value;
|
const head = mdr.item.edit.props?.header.value;
|
||||||
const PassProp = mdr.PassProp;
|
const PassProp = mdr.PassProp;
|
||||||
md.header.internalRender = () => set({});
|
|
||||||
md.header.render = () => set({});
|
md.header.render = () => set({});
|
||||||
|
|
||||||
|
const prefix = breadcrumbPrefix(md);
|
||||||
|
if (md.selected && md.header.child.breadcrumb) {
|
||||||
|
md.header.breadcrumb = [...prefix, ...md.header.child.breadcrumb()];
|
||||||
|
} else if (!md.selected && md.header.master.breadcrumb) {
|
||||||
|
md.header.breadcrumb = [...prefix, ...md.header.master.breadcrumb()];
|
||||||
|
}
|
||||||
|
|
||||||
return <PassProp md={md}>{head}</PassProp>;
|
return <PassProp md={md}>{head}</PassProp>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { useLocal } from "lib/utils/use-local";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { MDLocal, MDRef } from "../utils/typings";
|
import { MDLocal, MDRef } from "../utils/typings";
|
||||||
import { MDHeader } from "./MDHeader";
|
import { MDHeader } from "./MDHeader";
|
||||||
import { useLocal } from "lib/utils/use-local";
|
|
||||||
|
|
||||||
const w = window as unknown as {
|
const w = window as unknown as {
|
||||||
md_panel_master: any;
|
md_panel_master: any;
|
||||||
|
|
@ -18,14 +18,9 @@ export const MDRenderMaster: FC<{
|
||||||
local.md = on_init();
|
local.md = on_init();
|
||||||
}
|
}
|
||||||
const md = local.md;
|
const md = local.md;
|
||||||
|
md.header.master.breadcrumb = breadcrumb;
|
||||||
md.header.render = () => {
|
|
||||||
md.header.breadcrumb = breadcrumb();
|
|
||||||
md.header.internalRender();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
md.header.render();
|
|
||||||
if (md) {
|
if (md) {
|
||||||
let width = 0;
|
let width = 0;
|
||||||
let min_width = 0;
|
let min_width = 0;
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ export const editorMDInit = (md: MDLocal, mdr: MDRef, arg: MDProps) => {
|
||||||
];
|
];
|
||||||
md.status = "unready";
|
md.status = "unready";
|
||||||
} else {
|
} else {
|
||||||
// md.header.breadcrumb = [];
|
|
||||||
md.status = "ready";
|
md.status = "ready";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import { fetchLinkParams, parseLink } from "lib/comps/form/field/type/TypeLink";
|
||||||
import { MDLocal } from "./typings";
|
import { MDLocal } from "./typings";
|
||||||
|
import { BreadItem } from "lib/comps/custom/Breadcrumb";
|
||||||
|
|
||||||
export const masterDetailParseHash = (md: MDLocal) => {
|
export const masterDetailParseHash = (md: MDLocal) => {
|
||||||
let raw_hash = decodeURIComponent(location.hash);
|
let raw_hash = decodeURIComponent(location.hash);
|
||||||
|
|
@ -21,6 +23,27 @@ export const masterDetailParseHash = (md: MDLocal) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parsed_link = parseLink();
|
||||||
|
let changed = parsed_link.length !== md.params.links.length;
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
for (let i = 0; i < parsed_link.length; i++) {
|
||||||
|
if (parsed_link[i] !== md.params.links[i].hash) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
md.params.links = [];
|
||||||
|
md.header.loading = true;
|
||||||
|
fetchLinkParams(parsed_link).then((links) => {
|
||||||
|
md.params.links = links;
|
||||||
|
md.header.loading = false;
|
||||||
|
md.header.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const masterDetailApplyParams = (md: MDLocal) => {
|
export const masterDetailApplyParams = (md: MDLocal) => {
|
||||||
|
|
@ -56,3 +79,40 @@ export const masterDetailApplyParams = (md: MDLocal) => {
|
||||||
location.hash = hash;
|
location.hash = hash;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const breadcrumbPrefix = (md: MDLocal) => {
|
||||||
|
const prefix: BreadItem[] = [];
|
||||||
|
if (md.params.links && md.params.links.length > 0) {
|
||||||
|
const hashes: string[] = [];
|
||||||
|
for (const link of md.params.links) {
|
||||||
|
if (!hashes.includes(link.hash)) {
|
||||||
|
hashes.push(link.hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const link of md.params.links) {
|
||||||
|
for (const p of link.prefix) {
|
||||||
|
prefix.push({
|
||||||
|
label: p.label,
|
||||||
|
onClick(ev) {
|
||||||
|
let url = "";
|
||||||
|
|
||||||
|
const hashIndex = hashes.indexOf(link.hash);
|
||||||
|
const link_hashes = hashes.slice(0, hashIndex).join("+");
|
||||||
|
const lnk = link_hashes ? `#lnk=${link_hashes}` : ``;
|
||||||
|
|
||||||
|
if (p.md) {
|
||||||
|
url = `${link.url}#${p.md.name}=${p.md.value}${lnk}`;
|
||||||
|
} else {
|
||||||
|
url = `${link.url}${lnk}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
navigate(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prefix;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { BreadItem } from "@/comps/custom/Breadcrumb";
|
import { BreadItem } from "@/comps/custom/Breadcrumb";
|
||||||
import { FMLocal } from "@/comps/form/typings";
|
import { FMLocal } from "@/comps/form/typings";
|
||||||
import { GFCol } from "@/gen/utils";
|
import { GFCol } from "@/gen/utils";
|
||||||
|
import { LinkParam } from "lib/comps/form/field/type/TypeLink";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
type ID_MASTER_DETAIL = string;
|
type ID_MASTER_DETAIL = string;
|
||||||
|
|
@ -38,11 +39,11 @@ export type MDLocalInternal = {
|
||||||
title: string;
|
title: string;
|
||||||
status: "init" | "unready" | "ready";
|
status: "init" | "unready" | "ready";
|
||||||
header: {
|
header: {
|
||||||
|
loading: boolean;
|
||||||
breadcrumb: BreadItem[];
|
breadcrumb: BreadItem[];
|
||||||
internalRender: () => void;
|
|
||||||
render: () => void;
|
render: () => void;
|
||||||
master: { prefix: any; suffix: any };
|
master: { prefix: any; suffix: any; breadcrumb?: () => BreadItem[] };
|
||||||
child: { prefix: any; suffix: any };
|
child: { prefix: any; suffix: any; breadcrumb?: () => BreadItem[] };
|
||||||
};
|
};
|
||||||
actions: MDActions;
|
actions: MDActions;
|
||||||
selected: any;
|
selected: any;
|
||||||
|
|
@ -53,7 +54,8 @@ export type MDLocalInternal = {
|
||||||
internal: { action_should_refresh: boolean };
|
internal: { action_should_refresh: boolean };
|
||||||
master: { render: () => void };
|
master: { render: () => void };
|
||||||
params: {
|
params: {
|
||||||
hash: any;
|
links: LinkParam[];
|
||||||
|
hash: Record<string, any>;
|
||||||
tabs: any;
|
tabs: any;
|
||||||
parse: () => void;
|
parse: () => void;
|
||||||
apply: () => void;
|
apply: () => void;
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,41 @@
|
||||||
import { Skeleton } from "@/comps/ui/skeleton";
|
import { Skeleton } from "@/comps/ui/skeleton";
|
||||||
|
import { cn } from "lib/utils";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
export const FieldLoading = () => {
|
export const FieldLoading: FC<{ height?: "normal" | "short" }> = (prop) => {
|
||||||
|
let height = "10px";
|
||||||
|
if (prop.height === "short") height = "6px";
|
||||||
return (
|
return (
|
||||||
<div className="field-inner c-flex c-flex-col c-space-y-1 c-p-1 c-justify-center">
|
<div className="field-inner c-flex c-flex-col c-space-y-1 c-p-1 c-justify-center">
|
||||||
<div className="c-flex c-space-x-1">
|
<div className="c-flex c-space-x-1">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
className={css`
|
className={css`
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 10px;
|
height: ${height};
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
<Skeleton
|
<Skeleton
|
||||||
className={css`
|
className={css`
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 10px;
|
height: ${height};
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Skeleton
|
<Skeleton
|
||||||
className={css`
|
className={css`
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 10px;
|
height: ${height};
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Spinner = ({ className }: { className?: string }) => {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<Loader2 className={cx("c-h-4 c-w-4 c-animate-spin")} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
export { FieldLoading } from "@/comps/ui/field-loading";
|
export { FieldLoading } from "@/comps/ui/field-loading";
|
||||||
import { lazify, lazifyMany } from "@/utils/lazify";
|
import { lazify, lazifyMany } from "@/utils/lazify";
|
||||||
|
export { guessLabel } from "./utils/guess-label";
|
||||||
|
export { fetchLinkParams } from "./comps/form/field/type/TypeLink";
|
||||||
export { prasi_gen } from "./gen/prasi_gen";
|
export { prasi_gen } from "./gen/prasi_gen";
|
||||||
|
|
||||||
export const Popover = lazify(
|
export const Popover = lazify(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { validate } from "uuid";
|
||||||
|
|
||||||
|
export const guessLabel = (_obj: Record<string, any>, key?: string) => {
|
||||||
|
let label = "";
|
||||||
|
let obj = _obj;
|
||||||
|
if (key) obj = _obj[key];
|
||||||
|
|
||||||
|
const label_key =
|
||||||
|
Object.keys(obj)
|
||||||
|
.map((e) => e.toLowerCase())
|
||||||
|
.find((e) => e.includes("name") || e.includes("nama")) || "";
|
||||||
|
|
||||||
|
label = obj[label_key];
|
||||||
|
if (obj.length > 1) {
|
||||||
|
for (const v of Object.values(obj)) {
|
||||||
|
if (typeof v === "string" && v.length >= 2 && !validate(v)) {
|
||||||
|
label = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof label === "string" && label.length > 10) {
|
||||||
|
label = label.substring(0, 10) + "…";
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export const getPathname = (url?: string) => {
|
export const getPathname = (opt?: { hash?: boolean }) => {
|
||||||
if (
|
if (
|
||||||
["prasi.avolut.com"].includes(location.hostname) ||
|
["prasi.avolut.com"].includes(location.hostname) ||
|
||||||
location.host === "localhost:4550"
|
location.host === "localhost:4550"
|
||||||
|
|
@ -9,11 +9,8 @@ export const getPathname = (url?: string) => {
|
||||||
location.pathname.startsWith("/deploy")
|
location.pathname.startsWith("/deploy")
|
||||||
) {
|
) {
|
||||||
const hash = location.hash;
|
const hash = location.hash;
|
||||||
if (url?.startsWith("/prod")) {
|
|
||||||
return "/" + url.split("/").slice(3).join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hash !== "") {
|
if (hash !== "" && opt?.hash !== false) {
|
||||||
return "/" + location.pathname.split("/").slice(3).join("/") + hash;
|
return "/" + location.pathname.split("/").slice(3).join("/") + hash;
|
||||||
} else {
|
} else {
|
||||||
return "/" + location.pathname.split("/").slice(3).join("/");
|
return "/" + location.pathname.split("/").slice(3).join("/");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue