wip
This commit is contained in:
parent
1747269a04
commit
cf6b8b0728
|
|
@ -1,6 +1,7 @@
|
||||||
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 { Skeleton } from "../ui/skeleton";
|
||||||
|
import { MDLocal } from "../md/utils/typings";
|
||||||
|
|
||||||
export type BreadItem = {
|
export type BreadItem = {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
|
|
@ -15,33 +16,37 @@ type BreadcrumbProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
props?: any;
|
props?: any;
|
||||||
value?: BreadItem[];
|
value?: BreadItem[];
|
||||||
item?: any;
|
item?: any
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Breadcrumb: FC<BreadcrumbProps> = (_arg) => {
|
export const Breadcrumb: FC<BreadcrumbProps> = (_arg) => {
|
||||||
const { on_load, item } = _arg;
|
const { on_load, item } = _arg;
|
||||||
|
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
list: _arg.value || ([] as BreadItem[]),
|
list: _arg.value || ([] as BreadItem[]),
|
||||||
status: "init" as "init" | "loading" | "ready",
|
status: "init" as "init" | "loading" | "ready",
|
||||||
params: {},
|
params: {},
|
||||||
});
|
});
|
||||||
|
// code review: md.breadcrumb yang di set di props value dipindahkan di on_load
|
||||||
|
// dan ketika panjang _arg.value bernilai null maka status berubah menjadi init
|
||||||
|
// untuk menjalankan on_load
|
||||||
|
// case: ketika refreshBread dijalankan pada MasterDetail
|
||||||
|
// md.breadcrum yang awalnya array kosong akan berisi satu array namun
|
||||||
|
// md.breadcrumb yang diterima di komponen ini tidak berubah tetap seperti
|
||||||
|
// value default yakni array kosong
|
||||||
if (_arg.value) {
|
if (_arg.value) {
|
||||||
local.list = _arg.value;
|
local.list = _arg.value;
|
||||||
local.status = "ready";
|
local.status = "ready";
|
||||||
|
if(!_arg.value.length) local.status = "init"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local.status === "init") {
|
if (local.status === "init") {
|
||||||
let should_load = true;
|
let should_load = true;
|
||||||
local.status = "loading";
|
local.status = "loading";
|
||||||
|
|
||||||
if (isEditor && item && breadcrumbData[item.id]) {
|
if (isEditor && item && breadcrumbData[item.id]) {
|
||||||
local.list = breadcrumbData[item.id];
|
local.list = breadcrumbData[item.id];
|
||||||
local.status = "ready";
|
local.status = "ready";
|
||||||
should_load = false;
|
should_load = false;
|
||||||
|
if(!local.list.length) should_load = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (should_load && typeof on_load === "function") {
|
if (should_load && typeof on_load === "function") {
|
||||||
const callback = (res: any) => {
|
const callback = (res: any) => {
|
||||||
local.list = res;
|
local.list = res;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { FC, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export const MasterFilter: FC<{
|
||||||
|
name: string;
|
||||||
|
value: any;
|
||||||
|
child: any;
|
||||||
|
}> = ({name, value, child }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEditor) {
|
||||||
|
const w = window as any;
|
||||||
|
if (typeof w["prasi_filter"] !== "object") w["prasi_filter"] = {};
|
||||||
|
if (typeof w["prasi_filter"][name] !== "object")
|
||||||
|
w["prasi_filter"][name] = {};
|
||||||
|
const val = value();
|
||||||
|
w["prasi_filter"][name] = {
|
||||||
|
...w["prasi_filter"][name],
|
||||||
|
...val
|
||||||
|
};
|
||||||
|
w.prasiContext.render();
|
||||||
|
}
|
||||||
|
},[])
|
||||||
|
|
||||||
|
return <div>{child}</div>;
|
||||||
|
};
|
||||||
|
|
@ -10,7 +10,10 @@ import { formReload } from "./utils/reload";
|
||||||
|
|
||||||
const editorFormWidth = {} as Record<string, { w: number; f: any }>;
|
const editorFormWidth = {} as Record<string, { w: number; f: any }>;
|
||||||
|
|
||||||
|
export { FMLocal } from "./typings";
|
||||||
|
|
||||||
export const Form: FC<FMProps> = (props) => {
|
export const Form: FC<FMProps> = (props) => {
|
||||||
|
|
||||||
const { PassProp, body } = props;
|
const { PassProp, body } = props;
|
||||||
const fm = useLocal<FMInternal>({
|
const fm = useLocal<FMInternal>({
|
||||||
data: editorFormData[props.item.id]
|
data: editorFormData[props.item.id]
|
||||||
|
|
@ -104,7 +107,6 @@ export const Form: FC<FMProps> = (props) => {
|
||||||
formInit(fm, props);
|
formInit(fm, props);
|
||||||
fm.reload();
|
fm.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementsByClassName("prasi-toaster").length === 0) {
|
if (document.getElementsByClassName("prasi-toaster").length === 0) {
|
||||||
const elemDiv = document.createElement("div");
|
const elemDiv = document.createElement("div");
|
||||||
elemDiv.className = "prasi-toaster";
|
elemDiv.className = "prasi-toaster";
|
||||||
|
|
@ -119,7 +121,6 @@ export const Form: FC<FMProps> = (props) => {
|
||||||
}, 100);
|
}, 100);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,8 @@ export const Field: FC<FieldProp> = (arg) => {
|
||||||
if (field.status === "init" && !isEditor) return null;
|
if (field.status === "init" && !isEditor) return null;
|
||||||
|
|
||||||
const errors = fm.error.get(field.name);
|
const errors = fm.error.get(field.name);
|
||||||
|
|
||||||
const props = { ...arg.props };
|
const props = { ...arg.props };
|
||||||
delete props.className;
|
delete props.className;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={cx(
|
className={cx(
|
||||||
|
|
@ -59,6 +57,7 @@ export const Field: FC<FieldProp> = (arg) => {
|
||||||
_meta={arg._meta}
|
_meta={arg._meta}
|
||||||
_item={arg._item}
|
_item={arg._item}
|
||||||
_sync={arg._sync}
|
_sync={arg._sync}
|
||||||
|
arg={arg}
|
||||||
/>
|
/>
|
||||||
{field.desc && (
|
{field.desc && (
|
||||||
<div className={cx("c-p-2 c-text-xs", errors.length > 0 && "c-pb-1")}>
|
<div className={cx("c-p-2 c-text-xs", errors.length > 0 && "c-pb-1")}>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
import { createItem } from "@/gen/utils";
|
import { createItem } from "@/gen/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { FMLocal, FieldLocal } from "../typings";
|
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
||||||
import { genFieldMitem, updateFieldMItem } from "../utils/gen-mitem";
|
import { genFieldMitem, updateFieldMItem } from "../utils/gen-mitem";
|
||||||
import { fieldMapping } from "./mapping";
|
import { fieldMapping } from "./mapping";
|
||||||
import { FieldLoading } from "./raw/FieldLoading";
|
import { FieldLoading } from "./raw/FieldLoading";
|
||||||
import { TypeCustom } from "./type/TypeCustom";
|
import { TypeCustom } from "./type/TypeCustom";
|
||||||
|
import { FieldTypeText, PropTypeText } from "./type/TypeText";
|
||||||
|
import { TypeDropdown } from "./type/TypeDropdown";
|
||||||
|
import { FieldToggle } from "./type/TypeToggle";
|
||||||
|
import { SingleOption } from "./type/TypeSingleOption";
|
||||||
|
import { MultiOption } from "./type/TypeMultiOption";
|
||||||
|
|
||||||
const modify = {
|
const modify = {
|
||||||
timeout: null as any,
|
timeout: null as any,
|
||||||
|
|
@ -19,69 +24,69 @@ export const FieldInput: FC<{
|
||||||
_item: any;
|
_item: any;
|
||||||
_meta: any;
|
_meta: any;
|
||||||
_sync: (mitem: any, item: any) => void;
|
_sync: (mitem: any, item: any) => void;
|
||||||
}> = ({ field, fm, PassProp, child, _meta, _item, _sync }) => {
|
arg: FieldProp;
|
||||||
const prefix = typeof field.prefix === "function" ? field.prefix() : null;
|
}> = ({ field, fm, PassProp, child, _meta, _item, _sync, arg }) => {
|
||||||
const suffix = typeof field.suffix === "function" ? field.suffix() : null;
|
// return <></>
|
||||||
|
const prefix = typeof field.prefix === "function" ? field.prefix() : typeof field.prefix === "string" ? field.prefix : null;
|
||||||
|
const suffix = typeof field.suffix === "function" ? field.suffix() : typeof field.suffix === "string" ? field.prefix : null;
|
||||||
const errors = fm.error.get(field.name);
|
const errors = fm.error.get(field.name);
|
||||||
const childs = get(
|
const type_field = arg.type; // tipe field
|
||||||
child,
|
const sub_type_field = arg.sub_type;
|
||||||
"props.meta.item.component.props.child.content.childs"
|
// sudah gk pake children
|
||||||
);
|
// const childs = get(
|
||||||
|
// child,
|
||||||
|
// "props.meta.item.component.props.child.content.childs"
|
||||||
|
// );
|
||||||
|
|
||||||
let found = null as any;
|
// let found = null as any;
|
||||||
|
|
||||||
if (childs && childs.length > 0 && field.type !== "custom") {
|
// if (childs && childs.length > 0 && field.type !== "custom") {
|
||||||
for (const child of childs) {
|
// for (const child of childs) {
|
||||||
let mp = (fieldMapping as any)[field.type];
|
// let mp = (fieldMapping as any)[field.type];
|
||||||
|
// if (!mp) {
|
||||||
if (!mp) {
|
// mp = (fieldMapping as any)["text"];
|
||||||
mp = (fieldMapping as any)["text"];
|
// }
|
||||||
}
|
// if (child.component?.id === mp.id) {
|
||||||
|
// found = child;
|
||||||
if (child.component?.id === mp.id) {
|
// if (mp.props) {
|
||||||
found = child;
|
// const item = createItem({
|
||||||
|
// component: {
|
||||||
if (mp.props) {
|
// id: "--",
|
||||||
const item = createItem({
|
// props:
|
||||||
component: {
|
// typeof mp.props === "function" ? mp.props(fm, field) : mp.props,
|
||||||
id: "--",
|
// },
|
||||||
props:
|
// });
|
||||||
typeof mp.props === "function" ? mp.props(fm, field) : mp.props,
|
// const props = found.component.props;
|
||||||
},
|
// let should_update = false;
|
||||||
});
|
// for (const [k, v] of Object.entries(item.component.props) as any) {
|
||||||
|
// if (props[k] && props[k].valueBuilt === v.valueBuilt) {
|
||||||
const props = found.component.props;
|
// continue;
|
||||||
let should_update = false;
|
// } else {
|
||||||
|
// if (field.prop && !field.prop[k]) {
|
||||||
for (const [k, v] of Object.entries(item.component.props) as any) {
|
// props[k] = v;
|
||||||
if (props[k] && props[k].valueBuilt === v.valueBuilt) {
|
// should_update = true;
|
||||||
continue;
|
// }
|
||||||
} else {
|
// }
|
||||||
if (field.prop && !field.prop[k]) {
|
// }
|
||||||
props[k] = v;
|
// if (should_update) {
|
||||||
should_update = true;
|
// updateFieldMItem(_meta, found, _sync);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
if (should_update) {
|
// }
|
||||||
updateFieldMItem(_meta, found, _sync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isEditor && !found && field.type !== "custom") {
|
|
||||||
genFieldMitem({ _meta, _item, _sync, field, fm });
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (isEditor && !found && field.type !== "custom") {
|
||||||
|
// genFieldMitem({ _meta, _item, _sync, field, fm });
|
||||||
|
// }
|
||||||
|
// }, []);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"field-outer c-flex c-flex-1 c-flex-row c-rounded c-border c-text-sm",
|
!["toogle", "button", "radio","checkbox"].includes(arg.sub_type)
|
||||||
|
? "field-outer c-flex c-flex-1 c-flex-row c-rounded c-border c-text-sm"
|
||||||
|
: "",
|
||||||
fm.status === "loading"
|
fm.status === "loading"
|
||||||
? css`
|
? css`
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
|
@ -100,17 +105,44 @@ export const FieldInput: FC<{
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{prefix && <></>}
|
{prefix && prefix !== "" ? (
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
c-px-2 c-bg-gray-200 c-flex c-flex-row c-items-center"
|
||||||
|
>
|
||||||
|
{prefix}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
{fm.status === "loading" ? (
|
{fm.status === "loading" ? (
|
||||||
<FieldLoading />
|
<FieldLoading />
|
||||||
) : (
|
) : (
|
||||||
<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.disabled && "c-pointer-events-none"
|
field.disabled && "c-pointer-events-none",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{field.type === "custom" ? (
|
{/* <FieldToggle arg={arg} field={field} fm={fm} /> */}
|
||||||
|
{[
|
||||||
|
"date",
|
||||||
|
"input"
|
||||||
|
].includes(type_field) ? (
|
||||||
|
<FieldTypeText
|
||||||
|
field={field}
|
||||||
|
fm={fm}
|
||||||
|
prop={{ type: arg.type, sub_type: arg.sub_type, prefix, suffix} as PropTypeText}
|
||||||
|
/>
|
||||||
|
) : ["single-option"].includes(type_field) ? (
|
||||||
|
<SingleOption arg={arg} field={field} fm={fm} />
|
||||||
|
) : ["multi-option"].includes(type_field) ? (
|
||||||
|
<MultiOption arg={arg} field={field} fm={fm}/>
|
||||||
|
) : (
|
||||||
|
<>OTW</>
|
||||||
|
)}
|
||||||
|
{/* Komentar nanti diaktifkan setelah input */}
|
||||||
|
{/* {field.type === "custom" ? (
|
||||||
<TypeCustom fm={fm} field={field} />
|
<TypeCustom fm={fm} field={field} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
@ -121,10 +153,19 @@ export const FieldInput: FC<{
|
||||||
</PassProp>
|
</PassProp>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{suffix && <></>}
|
{suffix && suffix !== "" ? (
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
c-px-2 c-bg-gray-200 c-flex c-flex-row c-items-center"
|
||||||
|
>
|
||||||
|
{suffix}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,5 @@ export const fieldMapping: {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
switch: { id: ""},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
|
|
||||||
|
export const FieldButton: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
const local = useLocal({
|
||||||
|
list: [] as any[],
|
||||||
|
value: [] as any[],
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const callback = (res: any[]) => {
|
||||||
|
local.list = res;
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
local.value = res.map((e) => get(e, arg.pk));
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
const res = arg.on_load();
|
||||||
|
if (res instanceof Promise) res.then(callback);
|
||||||
|
else callback(res);
|
||||||
|
}, []);
|
||||||
|
let value: any = fm.data[field.name];
|
||||||
|
|
||||||
|
if (arg.type === "multi-option") {
|
||||||
|
value = fm.data[field.name] || [];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
`c-flex`,
|
||||||
|
css`
|
||||||
|
gap: 0.5rem;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{local.list.map((item) => {
|
||||||
|
let isChecked = false;
|
||||||
|
try {
|
||||||
|
isChecked = value.some((e: any) => e[arg.pk] === item[arg.pk]);
|
||||||
|
} catch (ex) {}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!Array.isArray(fm.data[field.name]))
|
||||||
|
fm.data[field.name] = [];
|
||||||
|
if (isChecked) {
|
||||||
|
fm.data[field.name] = fm.data[field.name].filter(
|
||||||
|
(e: any) => e[arg.pk] !== item[arg.pk]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
fm.data[field.name].push(item);
|
||||||
|
}
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
draggable="true"
|
||||||
|
role="button"
|
||||||
|
title="Hover chip"
|
||||||
|
className={cx(
|
||||||
|
isChecked ? "c-bg-gray-200" : "c-border c-border-gray-500",
|
||||||
|
" c-text-gray-700 c-h-8 c-px-3 c-w-max c-flex c-gap-2 c-items-center c-rounded-full hover:c-bg-gray-300 hover:c-bg-opacity-75 "
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="block text-sm font-medium">
|
||||||
|
{arg.on_row(item)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (Array.isArray(value)) value = null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
`c-grid c-grid-cols- c-flex-grow c-gap-2 c-rounded-md c-bg-gray-200 c-p-0.5`,
|
||||||
|
css`
|
||||||
|
grid-template-columns: repeat(
|
||||||
|
${local.list.length},
|
||||||
|
minmax(0, 1fr)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{local.list.map((e) => {
|
||||||
|
let checked = get(e, arg.pk) === value;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
onClick={() => {
|
||||||
|
fm.data[field.name] = get(e, arg.pk);
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
`${checked ? "c-bg-blue-500 c-text-white" : ""} `,
|
||||||
|
"c-block c-cursor-pointer c-select-none c-rounded-md c-p-1 c-text-center peer-checked: peer-checked:c-font-bold"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{arg.on_row(e)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { DMMF } from "../../../../../typings/runtime/library";
|
||||||
|
|
||||||
|
export const FieldCheckbox: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
const local = useLocal({
|
||||||
|
list: [] as any[],
|
||||||
|
value: [] as any[],
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const callback = (res: any[]) => {
|
||||||
|
local.list = res;
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
local.value = res.map((e) => get(e, arg.pk));
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
const res = arg.on_load();
|
||||||
|
if (res instanceof Promise) res.then(callback);
|
||||||
|
else callback(res);
|
||||||
|
}, []);
|
||||||
|
let value: any = Array.isArray(fm.data[field.name]) ?fm.data[field.name] : [];
|
||||||
|
console.log({value})
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||||
|
<div className={cx(`c-flex c-flex-col c-space-y-1 c-p-0.5`)}>
|
||||||
|
{local.list.map((item) => {
|
||||||
|
let isChecked = false;
|
||||||
|
try {
|
||||||
|
isChecked = value.some((e: any) => e[arg.pk] === item[arg.pk]);
|
||||||
|
} catch (ex) {}
|
||||||
|
console.log(item[arg.pk], isChecked)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
console.log(item);
|
||||||
|
if (!Array.isArray(fm.data[field.name]))
|
||||||
|
fm.data[field.name] = [];
|
||||||
|
console.log(isChecked);
|
||||||
|
if (isChecked) {
|
||||||
|
fm.data[field.name] = fm.data[field.name].filter(
|
||||||
|
(e: any) => e[arg.pk] !== item[arg.pk]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
fm.data[field.name].push(item);
|
||||||
|
}
|
||||||
|
fm.render();
|
||||||
|
console.log({data: fm.data})
|
||||||
|
}}
|
||||||
|
className="c-flex c-flex-row c-space-x-1 cursor-pointer c-items-center rounded-full p-0.5"
|
||||||
|
>
|
||||||
|
{isChecked ? (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="c-fill-sky-500"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m10.6 14.092l-2.496-2.496q-.14-.14-.344-.15q-.204-.01-.364.15t-.16.354q0 .194.16.354l2.639 2.638q.242.243.565.243q.323 0 .565-.243l5.477-5.477q.14-.14.15-.344q.01-.204-.15-.363q-.16-.16-.354-.16q-.194 0-.353.16L10.6 14.092ZM5.615 20q-.69 0-1.152-.462Q4 19.075 4 18.385V5.615q0-.69.463-1.152Q4.925 4 5.615 4h12.77q.69 0 1.152.463q.463.462.463 1.152v12.77q0 .69-.462 1.152q-.463.463-1.153.463H5.615Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M5.615 20q-.69 0-1.152-.462Q4 19.075 4 18.385V5.615q0-.69.463-1.152Q4.925 4 5.615 4h12.77q.69 0 1.152.463q.463.462.463 1.152v12.77q0 .69-.462 1.152q-.463.463-1.153.463H5.615Zm0-1h12.77q.23 0 .423-.192q.192-.193.192-.423V5.615q0-.23-.192-.423Q18.615 5 18.385 5H5.615q-.23 0-.423.192Q5 5.385 5 5.615v12.77q0 .23.192.423q.193.192.423.192Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
<div className="">{arg.on_row(item)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import { OptionItem, RawDropdown } from "../raw/Dropdown";
|
||||||
|
import { FieldLoading } from "../raw/FieldLoading";
|
||||||
|
import get from "lodash.get";
|
||||||
|
|
||||||
|
export const TypeDropdown: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
const input = useLocal({
|
||||||
|
list: null as null | any[],
|
||||||
|
pk: "",
|
||||||
|
});
|
||||||
|
const value = fm.data[field.name];
|
||||||
|
field.input = input;
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEditor && input.list === null) {
|
||||||
|
field.status = "loading";
|
||||||
|
input.pk = arg.pk;
|
||||||
|
field.render();
|
||||||
|
const callback = (arg: any[]) => {
|
||||||
|
input.list = arg;
|
||||||
|
field.status = "ready";
|
||||||
|
input.render();
|
||||||
|
};
|
||||||
|
const res = arg.on_load();
|
||||||
|
if (res instanceof Promise) res.then(callback);
|
||||||
|
else callback(res);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let list: OptionItem[] = [];
|
||||||
|
if (input.list && input.list.length) {
|
||||||
|
input.list.map((e: any) => {
|
||||||
|
let id = null;
|
||||||
|
try {
|
||||||
|
id = e[arg.pk];
|
||||||
|
} catch (ex: any) {
|
||||||
|
console.error(
|
||||||
|
"Error: PK Invalid atau tidak ditemukan, cek lagi keys yang ingin dijadikan value"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
list.push({
|
||||||
|
value: id,
|
||||||
|
label: arg.on_row(e),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let selected = null;
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
if (input.pk) selected = value[input.pk];
|
||||||
|
} else {
|
||||||
|
selected = value;
|
||||||
|
}
|
||||||
|
console.log({list})
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{field.status === "loading" ? (
|
||||||
|
<FieldLoading />
|
||||||
|
) : (
|
||||||
|
<RawDropdown
|
||||||
|
options={list}
|
||||||
|
value={selected}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (val === null) {
|
||||||
|
fm.data[field.name] = null;
|
||||||
|
fm.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (input.list && input.pk) {
|
||||||
|
for (const item of input.list) {
|
||||||
|
if (item[input.pk] === val) {
|
||||||
|
fm.data[field.name] = item;
|
||||||
|
fm.render();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full c-h-full"
|
||||||
|
disabled={field.disabled}
|
||||||
|
onFocus={() => {
|
||||||
|
field.focused = true;
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
field.focused = false;
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { FMLocal, FieldLocal } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import parser from "any-date-parser";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { argv } from "bun";
|
||||||
|
import { PropTypeText } from "./TypeText";
|
||||||
|
export const FieldMoney: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
prop: PropTypeText;
|
||||||
|
}> = ({ field, fm, prop }) => {
|
||||||
|
let type_field = prop.sub_type;
|
||||||
|
let value: any = fm.data[field.name];
|
||||||
|
const input = useLocal({
|
||||||
|
value: 0 as any,
|
||||||
|
display: false as any,
|
||||||
|
ref: null as any,
|
||||||
|
});
|
||||||
|
let display: any = null;
|
||||||
|
// console.log({ prop });
|
||||||
|
return (
|
||||||
|
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
input.display ? "c-hidden" : "",
|
||||||
|
"c-flex-grow c-px-2 c-flex c-flex-row c-items-center"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (input.ref) {
|
||||||
|
input.display = !input.display;
|
||||||
|
input.ref.focus();
|
||||||
|
input.render();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatMoney(Number(value) || 0)}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref={(el) => (input.ref = el)}
|
||||||
|
type={"number"}
|
||||||
|
onClick={() => {}}
|
||||||
|
onChange={(ev) => {
|
||||||
|
fm.data[field.name] = ev.currentTarget.value;
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
disabled={field.disabled}
|
||||||
|
className={cx(
|
||||||
|
!input.display ? "c-hidden" : "",
|
||||||
|
"c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
|
||||||
|
)}
|
||||||
|
spellCheck={false}
|
||||||
|
onFocus={() => {
|
||||||
|
field.focused = true;
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
console.log("blur");
|
||||||
|
field.focused = false;
|
||||||
|
input.display = !input.display;
|
||||||
|
input.render();
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const formatMoney = (res: number) => {
|
||||||
|
const formattedAmount = new Intl.NumberFormat("id-ID", {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
}).format(res);
|
||||||
|
return formattedAmount;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { TypeDropdown } from "./TypeDropdown";
|
||||||
|
import { FieldToggle } from "./TypeToggle";
|
||||||
|
import { FieldButton } from "./TypeButton";
|
||||||
|
import { FieldRadio } from "./TypeRadio";
|
||||||
|
import { FieldCheckbox } from "./TypeCheckbox";
|
||||||
|
|
||||||
|
export const MultiOption: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{arg.sub_type === "checkbox" ? (
|
||||||
|
<FieldCheckbox field={field} fm={fm} arg={arg}/>
|
||||||
|
) : arg.sub_type === "tag" ? (
|
||||||
|
<>{arg.sub_type}</>
|
||||||
|
) : arg.sub_type === "button" ? (
|
||||||
|
<FieldButton arg={arg} field={field} fm={fm} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
|
|
||||||
|
export const FieldRadio: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
const local = useLocal({
|
||||||
|
list: [] as any[],
|
||||||
|
value: [] as any[],
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const callback = (res: any[]) => {
|
||||||
|
local.list = res;
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
local.value = res.map((e) => get(e, arg.pk));
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
const res = arg.on_load();
|
||||||
|
if (res instanceof Promise) res.then(callback);
|
||||||
|
else callback(res);
|
||||||
|
}, []);
|
||||||
|
let listValue = [];
|
||||||
|
let value: any = fm.data[field.name];
|
||||||
|
let checked = local.value.indexOf(value) > 0 ? true : false;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||||
|
<div className={cx(`c-flex c-flex-col c-space-y-1 c-p-0.5`)}>
|
||||||
|
{local.list.map((e) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center mb-4"
|
||||||
|
onClick={() => {
|
||||||
|
fm.data[field.name] = get(e, arg.pk);
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="country-option-1"
|
||||||
|
type="radio"
|
||||||
|
name="countries"
|
||||||
|
value="USA"
|
||||||
|
className="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300"
|
||||||
|
aria-labelledby="country-option-1"
|
||||||
|
aria-describedby="country-option-1"
|
||||||
|
checked={get(e, arg.pk) === value}
|
||||||
|
/>
|
||||||
|
<label className="text-sm font-medium text-gray-900 ml-2 block">
|
||||||
|
{arg.on_row(e)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
className={cx(
|
||||||
|
`${
|
||||||
|
get(e, arg.pk) === value
|
||||||
|
? "c-bg-blue-500 c-text-white"
|
||||||
|
: ""
|
||||||
|
} `,
|
||||||
|
"c-block c-cursor-pointer c-select-none c-rounded-md c-p-1 c-text-center peer-checked: peer-checked:c-font-bold"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{arg.on_row(e)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import Quill from "quill";
|
||||||
|
import "quill/dist/quill.snow.css"; // Import CSS untuk tema Quill
|
||||||
|
import get from "lodash.get";
|
||||||
|
export const FieldRichText: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
const local = useLocal({
|
||||||
|
ref: null as any,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (local.ref) {
|
||||||
|
console.log(local.ref);
|
||||||
|
console.log(local.ref.current);
|
||||||
|
const q = new Quill(local.ref, {
|
||||||
|
theme: "snow",
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
["bold", "italic", "underline", "strike"], // toggled buttons
|
||||||
|
["blockquote", "code-block"],
|
||||||
|
["link", "image", "video", "formula"],
|
||||||
|
|
||||||
|
[{ header: 1 }, { header: 2 }], // custom button values
|
||||||
|
[{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
|
||||||
|
[{ script: "sub" }, { script: "super" }], // superscript/subscript
|
||||||
|
[{ indent: "-1" }, { indent: "+1" }], // outdent/indent
|
||||||
|
[{ direction: "rtl" }], // text direction
|
||||||
|
|
||||||
|
[{ size: ["small", false, "large", "huge"] }], // custom dropdown
|
||||||
|
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||||
|
|
||||||
|
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
|
||||||
|
[{ font: [] }],
|
||||||
|
[{ align: [] }],
|
||||||
|
|
||||||
|
["clean"], // remove formatting button
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
let value: any = fm.data[field.name];
|
||||||
|
return (
|
||||||
|
<div className="c-flex c-flex-col c-w-full">
|
||||||
|
<div
|
||||||
|
ref={(e) => (local.ref = e)}
|
||||||
|
className={cx(css`
|
||||||
|
height: 20rem !important;
|
||||||
|
`)}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { TypeDropdown } from "./TypeDropdown";
|
||||||
|
import { FieldToggle } from "./TypeToggle";
|
||||||
|
import { FieldButton } from "./TypeButton";
|
||||||
|
import { FieldRadio } from "./TypeRadio";
|
||||||
|
|
||||||
|
export const SingleOption: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{arg.sub_type === "dropdown" ? (
|
||||||
|
<TypeDropdown arg={arg} field={field} fm={fm} />
|
||||||
|
) : arg.sub_type === "toogle" ? (
|
||||||
|
<FieldToggle arg={arg} field={field} fm={fm} />
|
||||||
|
) : arg.sub_type === "button" ? (
|
||||||
|
<FieldButton arg={arg} field={field} fm={fm} />
|
||||||
|
) : arg.sub_type === "radio" ? (
|
||||||
|
<FieldRadio arg={arg} field={field} fm={fm} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { Label } from "@/comps/ui/label";
|
||||||
|
import { Switch } from "@/comps/ui/switch";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { Button } from "@/comps/ui/button";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from "@/comps/ui/form";
|
||||||
|
import { FC } from "react";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
|
||||||
|
export type PropTypeSwitch = {};
|
||||||
|
export const FieldTypeSwitch: FC<{
|
||||||
|
valueName: string;
|
||||||
|
description?: string;
|
||||||
|
label?: string;
|
||||||
|
}> = ({ valueName, description, label }) => {
|
||||||
|
const local = useLocal({ checked: false });
|
||||||
|
// const FormSchema = z.object({
|
||||||
|
// valueName: z.boolean().default(false).optional(),
|
||||||
|
// });
|
||||||
|
// const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
// resolver: zodResolver(FormSchema),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||||
|
// console.log({ data });
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
local.checked = !local.checked;
|
||||||
|
local.render();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{JSON.stringify(local.checked)}
|
||||||
|
</div>
|
||||||
|
// <Form {...form}>
|
||||||
|
// <form onSubmit={form.handleSubmit(onSubmit)} className="w-full space-y-6">
|
||||||
|
// <div>
|
||||||
|
// <h3 className="mb-4 text-lg font-medium">
|
||||||
|
// {label != undefined ? label : "-"}
|
||||||
|
// </h3>
|
||||||
|
// <div className="space-y-4">
|
||||||
|
// <FormField
|
||||||
|
// control={form.control}
|
||||||
|
// name="first_value"
|
||||||
|
// render={({ field }) => (
|
||||||
|
// <FormItem className="flex items-center space-x-2">
|
||||||
|
// <div>
|
||||||
|
// <FormLabel className="text-base">
|
||||||
|
// {label != undefined ? label : "-"}
|
||||||
|
// </FormLabel>
|
||||||
|
// <FormDescription>
|
||||||
|
// {description != undefined ? description : "-"}
|
||||||
|
// </FormDescription>
|
||||||
|
// </div>
|
||||||
|
// <FormControl>
|
||||||
|
// <Switch
|
||||||
|
// checked={field.value}
|
||||||
|
// onCheckedChange={field.onChange}
|
||||||
|
// />
|
||||||
|
// </FormControl>
|
||||||
|
// </FormItem>
|
||||||
|
// )}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <Button type="submit">Submit</Button>
|
||||||
|
// </form>
|
||||||
|
// </Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -3,9 +3,31 @@ import { FMLocal, FieldLocal } from "../../typings";
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import parser from "any-date-parser";
|
import parser from "any-date-parser";
|
||||||
import { AutoHeightTextarea } from "@/comps/custom/AutoHeightTextarea";
|
import { AutoHeightTextarea } from "@/comps/custom/AutoHeightTextarea";
|
||||||
|
import { M } from "src/data/unitShortcuts";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { FieldMoney } from "./TypeMoney";
|
||||||
|
import { FieldUpload } from "./TypeUpload";
|
||||||
|
import { FieldRichText } from "./TypeRichText";
|
||||||
|
|
||||||
export type PropTypeText = {
|
export type PropTypeText = {
|
||||||
type: "text" | "password" | "number" | "date" | "datetime" | "textarea";
|
type: "text" | "date";
|
||||||
|
sub_type:
|
||||||
|
| "text"
|
||||||
|
| "password"
|
||||||
|
| "number"
|
||||||
|
| "date"
|
||||||
|
| "datetime"
|
||||||
|
| "textarea"
|
||||||
|
| "datetime-local"
|
||||||
|
| "time"
|
||||||
|
| "money"
|
||||||
|
| "rich-text"
|
||||||
|
| "upload"
|
||||||
|
| "file"
|
||||||
|
| "password";
|
||||||
|
suffix: string;
|
||||||
|
prefix: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parse = parser.exportAsFunctionAny("en-US");
|
const parse = parser.exportAsFunctionAny("en-US");
|
||||||
|
|
@ -15,24 +37,46 @@ export const FieldTypeText: FC<{
|
||||||
fm: FMLocal;
|
fm: FMLocal;
|
||||||
prop: PropTypeText;
|
prop: PropTypeText;
|
||||||
}> = ({ field, fm, prop }) => {
|
}> = ({ field, fm, prop }) => {
|
||||||
|
let type_field = prop.sub_type;
|
||||||
|
switch (type_field) {
|
||||||
|
case "datetime":
|
||||||
|
type_field = "datetime-local";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
const input = useLocal({});
|
const input = useLocal({});
|
||||||
|
let display: any = null;
|
||||||
let value: any = fm.data[field.name];
|
let value: any = fm.data[field.name];
|
||||||
|
// let value: any = "2024-05-14T05:58:01.376Z" // case untuk date time
|
||||||
field.input = input;
|
field.input = input;
|
||||||
field.prop = prop;
|
field.prop = prop;
|
||||||
|
if (["date", "datetime", "datetime-local", "time"].includes(type_field)) {
|
||||||
if (["date", "datetime"].includes(prop.type)) {
|
if (typeof value === "string" || value instanceof Date) {
|
||||||
if (typeof value === "string") {
|
|
||||||
let date = parse(value);
|
let date = parse(value);
|
||||||
if (typeof date === "object" && date instanceof Date) {
|
if (typeof date === "object" && date instanceof Date) {
|
||||||
if (prop.type === "date") value = date.toISOString().substring(0, 10);
|
if (type_field === "date") value = format(date, "yyyy-MM-dd");
|
||||||
else if (prop.type === "datetime") value = date.toISOString();
|
else if (type_field === "datetime-local")
|
||||||
|
value = format(date, "yyyy-MM-dd HH:mm");
|
||||||
|
else if (type_field === "time") value = format(date, "HH:mm");
|
||||||
|
} else if (type_field === "time") {
|
||||||
|
if (value && !isTimeString(value)) value = null;
|
||||||
|
} else {
|
||||||
|
value = null;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
let date = parse(value);
|
||||||
|
if (typeof date === "object" && date instanceof Date) {
|
||||||
|
value = date.toISOString();
|
||||||
}
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
} else if (["number"].includes(type_field)) {
|
||||||
|
value = Number(value) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{prop.type === "textarea" ? (
|
{type_field === "textarea" ? (
|
||||||
<AutoHeightTextarea
|
<AutoHeightTextarea
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
fm.data[field.name] = ev.currentTarget.value;
|
fm.data[field.name] = ev.currentTarget.value;
|
||||||
|
|
@ -51,19 +95,81 @@ export const FieldTypeText: FC<{
|
||||||
field.render();
|
field.render();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : type_field === "upload" ? (
|
||||||
|
<>
|
||||||
|
<FieldUpload field={field} fm={fm} prop={prop} />
|
||||||
|
{/* <input
|
||||||
|
type="file"
|
||||||
|
id="avatar"
|
||||||
|
name="avatar"
|
||||||
|
accept="image/png, image/jpeg"
|
||||||
|
onChange={async (event: any) => {
|
||||||
|
let file = null;
|
||||||
|
try {
|
||||||
|
file = event.target.files[0];
|
||||||
|
} catch (ex) {}
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
const response = await fetch(
|
||||||
|
"https://prasi.avolut.com/_proxy/https%3A%2F%2Feam.avolut.com%2F_upload",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const contentType: any = response.headers.get("content-type");
|
||||||
|
let result;
|
||||||
|
if (contentType.includes("application/json")) {
|
||||||
|
result = await response.json();
|
||||||
|
} else if (contentType.includes("text/plain")) {
|
||||||
|
result = await response.text();
|
||||||
|
} else {
|
||||||
|
result = await response.blob();
|
||||||
|
}
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
fm.data[field.name] = get(result, "[0]");
|
||||||
|
fm.render();
|
||||||
|
} else {
|
||||||
|
alert("Error upload");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
</>
|
||||||
|
) : type_field === "money" ? (
|
||||||
|
<>
|
||||||
|
<FieldMoney field={field} fm={fm} prop={prop} />
|
||||||
|
</>
|
||||||
|
) : type_field === "rich-text" ? (
|
||||||
|
<>
|
||||||
|
<FieldRichText field={field} fm={fm} prop={prop} />
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
type={prop.type}
|
type={type_field}
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
|
console.log("onchange");
|
||||||
|
if (["date", "datetime", "datetime-local"].includes(type_field)) {
|
||||||
|
let result = null;
|
||||||
|
try {
|
||||||
|
result = new Date(ev.currentTarget.value);
|
||||||
|
} catch (ex) {}
|
||||||
|
fm.data[field.name] = result;
|
||||||
|
} else {
|
||||||
fm.data[field.name] = ev.currentTarget.value;
|
fm.data[field.name] = ev.currentTarget.value;
|
||||||
|
}
|
||||||
fm.render();
|
fm.render();
|
||||||
}}
|
}}
|
||||||
value={value || ""}
|
value={value}
|
||||||
disabled={field.disabled}
|
disabled={field.disabled}
|
||||||
className="c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
|
className="c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
field.focused = true;
|
field.focused = true;
|
||||||
|
display = "halo dek";
|
||||||
field.render();
|
field.render();
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
|
|
@ -75,3 +181,8 @@ export const FieldTypeText: FC<{
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const isTimeString = (time: any) => {
|
||||||
|
// Regex untuk deteksi string ini tipenya time
|
||||||
|
const timePattern = /^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/;
|
||||||
|
return timePattern.test(time);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import parser from "any-date-parser";
|
||||||
|
import { AutoHeightTextarea } from "@/comps/custom/AutoHeightTextarea";
|
||||||
|
import { M } from "src/data/unitShortcuts";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import get from "lodash.get";
|
||||||
|
|
||||||
|
export const FieldToggle: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
arg: FieldProp;
|
||||||
|
}> = ({ field, fm, arg }) => {
|
||||||
|
const local = useLocal({
|
||||||
|
list: [] as any[],
|
||||||
|
value:[] as any[]
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const callback = (res: any[]) => {
|
||||||
|
local.list = res;
|
||||||
|
if(Array.isArray(res)){
|
||||||
|
local.value = res.map((e) => get(e, arg.pk))
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
const res = arg.on_load();
|
||||||
|
if (res instanceof Promise) res.then(callback);
|
||||||
|
else callback(res);
|
||||||
|
}, []);
|
||||||
|
let listValue = []
|
||||||
|
let value: any = fm.data[field.name];
|
||||||
|
let checked = local.value.indexOf(value) > 0 ? true: false;
|
||||||
|
|
||||||
|
if(local.list.length < 2){
|
||||||
|
return <>Minimum dan maksimal 2 Data</>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"c-flex c-items-center c-justify-start c-w-full"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<label className="c-flex c-items-center c-cursor-pointer">
|
||||||
|
<div className="c-mr-3 c-text-gray-700 c-font-medium">{get(local,"list[0].label")}</div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"c-relative",
|
||||||
|
css`
|
||||||
|
input:checked ~ .dot {
|
||||||
|
transform: translateX(100%);
|
||||||
|
background-color: #48bb78;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input type="checkbox" id="toggleB" checked={checked} className="c-sr-only" onChange={(e) => {
|
||||||
|
const check = e.target.checked;
|
||||||
|
|
||||||
|
if(check){
|
||||||
|
fm.data[field.name] = local.value[1];
|
||||||
|
}else{
|
||||||
|
fm.data[field.name] = local.value[0];
|
||||||
|
}
|
||||||
|
fm.render();
|
||||||
|
console.log({data: fm.data})
|
||||||
|
// if(val) value = local.list[0];
|
||||||
|
// value = local.list[1]
|
||||||
|
}}/>
|
||||||
|
<div className="c-block c-bg-gray-600 c-w-8 c-h-5 c-rounded-full"></div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"dot c-absolute c-left-1 c-top-1 c-bg-white c-w-3 c-h-3 c-rounded-full c-transition"
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div className="c-ml-3 c-text-gray-700 c-font-medium">{get(local,"list[1].label")}</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { FMLocal, FieldLocal } from "../../typings";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import parser from "any-date-parser";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { argv } from "bun";
|
||||||
|
import { PropTypeText } from "./TypeText";
|
||||||
|
export const FieldUpload: FC<{
|
||||||
|
field: FieldLocal;
|
||||||
|
fm: FMLocal;
|
||||||
|
prop: PropTypeText;
|
||||||
|
}> = ({ field, fm, prop }) => {
|
||||||
|
let type_field = prop.sub_type;
|
||||||
|
let value: any = fm.data[field.name];
|
||||||
|
const input = useLocal({
|
||||||
|
value: 0 as any,
|
||||||
|
display: false as any,
|
||||||
|
ref: null as any,
|
||||||
|
drop: false as boolean
|
||||||
|
});
|
||||||
|
let display: any = null;
|
||||||
|
return (
|
||||||
|
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
|
||||||
|
<div
|
||||||
|
onDrop={(e: any) => {
|
||||||
|
console.log({ e });
|
||||||
|
e.preventDefault();
|
||||||
|
input.drop = false;
|
||||||
|
input.render();
|
||||||
|
}}
|
||||||
|
onDragOver={(e:any) => {
|
||||||
|
console.log("File(s) in drop zone");
|
||||||
|
|
||||||
|
// Prevent default behavior (Prevent file from being opened)
|
||||||
|
e.preventDefault();
|
||||||
|
input.drop = true;
|
||||||
|
input.render();
|
||||||
|
}}
|
||||||
|
className={cx(input.drop ? "c-bg-gray-100": "", "hover:c-bg-gray-100 c-m-1 c-relative c-flex-grow c-p-4 c-items-center c-flex c-flex-row c-text-gray-400 c-border c-border-gray-200 c-border-dashed c-rounded c-cursor-pointer")}
|
||||||
|
>
|
||||||
|
<div className="c-flex-row c-flex c-flex-grow c-space-x-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M3.5 13.5h-2a1 1 0 0 1-1-1v-8h13v8a1 1 0 0 1-1 1h-2" />
|
||||||
|
<path d="M4.5 10L7 7.5L9.5 10M7 7.5v6M11.29 1a1 1 0 0 0-.84-.5h-6.9a1 1 0 0 0-.84.5L.5 4.5h13zM7 .5v4" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div className="c-flex c-flex-col">
|
||||||
|
<span className="c-font-medium">
|
||||||
|
Drop Your File or{" "}
|
||||||
|
<span className="c-underline c-text-blue-500">Browse</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref={(ref) => (input.ref = ref)}
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
onChange={async (event: any) => {
|
||||||
|
let file = null;
|
||||||
|
try {
|
||||||
|
file = event.target.files[0];
|
||||||
|
} catch (ex) {}
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
const response = await fetch(
|
||||||
|
"https://prasi.avolut.com/_proxy/https%3A%2F%2Feam.avolut.com%2F_upload",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const contentType: any = response.headers.get("content-type");
|
||||||
|
let result;
|
||||||
|
if (contentType.includes("application/json")) {
|
||||||
|
result = await response.json();
|
||||||
|
} else if (contentType.includes("text/plain")) {
|
||||||
|
result = await response.text();
|
||||||
|
} else {
|
||||||
|
result = await response.blob();
|
||||||
|
}
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
fm.data[field.name] = get(result, "[0]");
|
||||||
|
fm.render();
|
||||||
|
} else {
|
||||||
|
alert("Error upload");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"c-absolute c-w-full c-h-full c-cursor-pointer c-top-0 c-left-0 c-hidden"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
// console.log({ prop });
|
||||||
|
return (
|
||||||
|
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
input.display ? "c-hidden" : "",
|
||||||
|
"c-flex-grow c-px-2 c-flex c-flex-row c-items-center"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (input.ref) {
|
||||||
|
input.display = !input.display;
|
||||||
|
input.ref.focus();
|
||||||
|
input.render();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatMoney(Number(value) || 0)}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref={(el) => (input.ref = el)}
|
||||||
|
type={"number"}
|
||||||
|
onClick={() => {}}
|
||||||
|
onChange={(ev) => {
|
||||||
|
fm.data[field.name] = ev.currentTarget.value;
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
disabled={field.disabled}
|
||||||
|
className={cx(
|
||||||
|
!input.display ? "c-hidden" : "",
|
||||||
|
"c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
|
||||||
|
)}
|
||||||
|
spellCheck={false}
|
||||||
|
onFocus={() => {
|
||||||
|
field.focused = true;
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
console.log("blur");
|
||||||
|
field.focused = false;
|
||||||
|
input.display = !input.display;
|
||||||
|
input.render();
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const formatMoney = (res: number) => {
|
||||||
|
const formattedAmount = new Intl.NumberFormat("id-ID", {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
}).format(res);
|
||||||
|
return formattedAmount;
|
||||||
|
};
|
||||||
|
|
@ -6,6 +6,7 @@ import { editorFormData } from "./utils/ed-data";
|
||||||
import { PropTypeText } from "./field/type/TypeText";
|
import { PropTypeText } from "./field/type/TypeText";
|
||||||
import { PropTypeRelation } from "./field/type/TypeRelation";
|
import { PropTypeRelation } from "./field/type/TypeRelation";
|
||||||
import { getProp } from "../../..";
|
import { getProp } from "../../..";
|
||||||
|
import { PropTypeSwitch } from "./field/type/TypeSwitch";
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -32,7 +33,7 @@ export type FieldProp = {
|
||||||
desc?: string;
|
desc?: string;
|
||||||
props?: any;
|
props?: any;
|
||||||
fm: FMLocal;
|
fm: FMLocal;
|
||||||
type: "text" | "relation";
|
type: "text" | "relation" | "switch" | "input" | "single-option"| "multi-option";
|
||||||
// | "number"
|
// | "number"
|
||||||
// | "textarea"
|
// | "textarea"
|
||||||
// | "dropdown"
|
// | "dropdown"
|
||||||
|
|
@ -59,6 +60,10 @@ export type FieldProp = {
|
||||||
_item: any;
|
_item: any;
|
||||||
_sync: any;
|
_sync: any;
|
||||||
custom?: () => CustomField;
|
custom?: () => CustomField;
|
||||||
|
on_load: () => any | Promise<any>;
|
||||||
|
on_row: (row: any) => string;
|
||||||
|
pk: string;
|
||||||
|
sub_type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FMInternal = {
|
export type FMInternal = {
|
||||||
|
|
@ -103,6 +108,7 @@ type FieldInternalProp = {
|
||||||
text: PropTypeText;
|
text: PropTypeText;
|
||||||
number: PropTypeText;
|
number: PropTypeText;
|
||||||
relation: PropTypeRelation;
|
relation: PropTypeRelation;
|
||||||
|
switch: PropTypeSwitch;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldInternal<T extends FieldProp["type"]> = {
|
export type FieldInternal<T extends FieldProp["type"]> = {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ export const genFieldMitem = (arg: {
|
||||||
?.get("child")
|
?.get("child")
|
||||||
?.get("content")
|
?.get("content")
|
||||||
?.get("childs");
|
?.get("childs");
|
||||||
|
|
||||||
let component = fieldMapping[field.type as "text"];
|
let component = fieldMapping[field.type as "text"];
|
||||||
|
|
||||||
if (!component) {
|
if (!component) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import { FC, MouseEvent } from "react";
|
||||||
|
import ExcelJS from "exceljs";
|
||||||
|
import * as FileSaver from "file-saver";
|
||||||
|
|
||||||
|
export const ExportExcel: FC<{
|
||||||
|
data: any[],
|
||||||
|
fileName?: string
|
||||||
|
}> = ({
|
||||||
|
data, fileName = "exported_data.xlsx"
|
||||||
|
}): JSX.Element => {
|
||||||
|
const local = useLocal({
|
||||||
|
data: [] as any[]
|
||||||
|
});
|
||||||
|
local.data = data;
|
||||||
|
local.render();
|
||||||
|
const getAllKeys = (arr: Array<Record<string, any>>): string[] => {
|
||||||
|
const keysSet = new Set<string>();
|
||||||
|
|
||||||
|
arr.forEach(obj => {
|
||||||
|
Object.keys(obj).forEach(key => keysSet.add(key));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(keysSet);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExport = async (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
try {
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
const worksheet = workbook.addWorksheet("Sheet 1");
|
||||||
|
|
||||||
|
const columns = getAllKeys(local.data);
|
||||||
|
worksheet.addRow(columns);
|
||||||
|
|
||||||
|
local.data.forEach((row) => {
|
||||||
|
const values = columns.map((col) => row[col]);
|
||||||
|
worksheet.addRow(values);
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
|
|
||||||
|
const blob = new Blob([buffer], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
FileSaver.saveAs(blob, fileName);
|
||||||
|
|
||||||
|
console.log("Data exported");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error exporting data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={handleExport} style={{background: '#00ffff'}}>Export</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
import { GFCol } from "@/gen/utils";
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import { ChangeEvent, FC, MouseEvent } from "react";
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
|
|
||||||
|
type ImportExcelProps = {
|
||||||
|
gen_fields: string[];
|
||||||
|
gen_table: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ImportExcel: FC<ImportExcelProps> = ({
|
||||||
|
gen_fields,
|
||||||
|
gen_table,
|
||||||
|
}) => {
|
||||||
|
const local = useLocal({
|
||||||
|
data: [] as any[],
|
||||||
|
columns: [] as string[],
|
||||||
|
fields: [] as string[],
|
||||||
|
tableName: "",
|
||||||
|
selectedRows: [] as {
|
||||||
|
pk: string | number;
|
||||||
|
rows: any;
|
||||||
|
}[],
|
||||||
|
pk: null as null | GFCol,
|
||||||
|
columnMappings: [] as Array<Record<string, string>>,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pk = local.pk?.name || "id";
|
||||||
|
|
||||||
|
const getAllKeys = (arr: Array<Record<string, any>>): string[] => {
|
||||||
|
const keysSet = new Set<string>();
|
||||||
|
|
||||||
|
arr.forEach((obj) => {
|
||||||
|
Object.keys(obj).forEach((key) => keysSet.add(key));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(keysSet);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (event: ProgressEvent<FileReader>) => {
|
||||||
|
if (!event.target?.result) return;
|
||||||
|
|
||||||
|
const workbook = XLSX.read(event.target.result, { type: "binary" });
|
||||||
|
const sheetName = workbook.SheetNames[0];
|
||||||
|
const sheet = workbook.Sheets[sheetName];
|
||||||
|
const sheetData = XLSX.utils.sheet_to_json(sheet);
|
||||||
|
local.data = sheetData;
|
||||||
|
local.columns = getAllKeys(local.data);
|
||||||
|
gen_fields.forEach((data: any) => {
|
||||||
|
local.fields.push(JSON.parse(data).name);
|
||||||
|
});
|
||||||
|
local.tableName = gen_table;
|
||||||
|
local.render();
|
||||||
|
};
|
||||||
|
reader.readAsBinaryString(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeImport = async (
|
||||||
|
columnMappings: string[],
|
||||||
|
data: {
|
||||||
|
pk: string | number;
|
||||||
|
rows: any;
|
||||||
|
}[]
|
||||||
|
) => {
|
||||||
|
const table = (db as any)[local.tableName];
|
||||||
|
if (table) {
|
||||||
|
// execute
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerCheckboxClick = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
local.data.forEach((data) => {
|
||||||
|
local.selectedRows.push({
|
||||||
|
pk: data[pk],
|
||||||
|
rows: data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
local.render();
|
||||||
|
console.log("Select All", local.selectedRows);
|
||||||
|
} else {
|
||||||
|
local.selectedRows = [];
|
||||||
|
local.render();
|
||||||
|
console.log("Deselect all", local.selectedRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkboxClick = (rowId: any) => (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const checked = !!local.selectedRows.find((data) => data.pk === rowId);
|
||||||
|
console.log(checked);
|
||||||
|
if (!checked) {
|
||||||
|
const checkedRowData = local.data.filter((row) => row[pk] === rowId);
|
||||||
|
local.selectedRows.push({
|
||||||
|
pk: rowId,
|
||||||
|
rows: checkedRowData,
|
||||||
|
});
|
||||||
|
local.render();
|
||||||
|
console.log("selected", local.selectedRows);
|
||||||
|
} else {
|
||||||
|
local.selectedRows = local.selectedRows.filter(
|
||||||
|
(data) => data.pk !== rowId
|
||||||
|
);
|
||||||
|
local.render();
|
||||||
|
console.log("deselected", local.selectedRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnMappingChange =
|
||||||
|
(col: string) => (e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const selectedValue = e.target.value;
|
||||||
|
local.columnMappings.push({
|
||||||
|
column: col,
|
||||||
|
selectedColumn: selectedValue,
|
||||||
|
});
|
||||||
|
local.render();
|
||||||
|
console.log("column mappings", local.columnMappings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRowChecked = (id: any) => {
|
||||||
|
return local.selectedRows.some((checked) => checked.pk === id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isConfirmed = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
console.log("selected rows", local.selectedRows);
|
||||||
|
console.log("column mappings", local.columnMappings);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input type="file" onChange={handleFileUpload} />
|
||||||
|
{local.data.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={isConfirmed}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#4CAF50",
|
||||||
|
border: "none",
|
||||||
|
color: "white",
|
||||||
|
padding: "15px 32px",
|
||||||
|
textAlign: "center",
|
||||||
|
textDecoration: "none",
|
||||||
|
display: "inline-block",
|
||||||
|
fontSize: "16px",
|
||||||
|
margin: "4px 2px",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Confirm Import
|
||||||
|
</button>
|
||||||
|
<h2>Imported Data:</h2>
|
||||||
|
<table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ border: "1px solid black", padding: "8px" }}>
|
||||||
|
<input type="checkbox" onChange={headerCheckboxClick} />
|
||||||
|
</th>
|
||||||
|
{local.columns.map((col) => (
|
||||||
|
<th
|
||||||
|
key={col}
|
||||||
|
style={{ border: "1px solid black", padding: "8px" }}
|
||||||
|
>
|
||||||
|
{col}{" "}
|
||||||
|
<select
|
||||||
|
onChange={columnMappingChange(col)}
|
||||||
|
style={{
|
||||||
|
marginLeft: "8px",
|
||||||
|
padding: "4px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Select Field</option>
|
||||||
|
{local.fields.map((field) => (
|
||||||
|
<option key={field} value={field}>
|
||||||
|
{field}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{local.data.map((row, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td style={{ border: "1px solid black", padding: "8px" }}>
|
||||||
|
<div
|
||||||
|
onClick={checkboxClick(row[pk])}
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`,
|
||||||
|
"c-flex c-items-center c-justify-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="c-pointer-events-none"
|
||||||
|
type="checkbox"
|
||||||
|
checked={isRowChecked(row[pk])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{local.columns.map((col) => (
|
||||||
|
<td
|
||||||
|
key={col}
|
||||||
|
style={{ border: "1px solid black", padding: "8px" }}
|
||||||
|
>
|
||||||
|
{row[col]}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -3,11 +3,15 @@ import { fields_map } from "@/utils/format-value";
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { FC, useEffect } from "react";
|
import { ChangeEvent, FC, MouseEvent, useEffect, useState } from "react";
|
||||||
import DataGrid, {
|
import DataGrid, {
|
||||||
ColumnOrColumnGroup,
|
ColumnOrColumnGroup,
|
||||||
Row,
|
Row,
|
||||||
SortColumn,
|
SortColumn,
|
||||||
|
SelectColumn,
|
||||||
|
textEditor,
|
||||||
|
RenderCheckboxProps,
|
||||||
|
SELECT_COLUMN_KEY,
|
||||||
} from "react-data-grid";
|
} from "react-data-grid";
|
||||||
import "react-data-grid/lib/styles.css";
|
import "react-data-grid/lib/styles.css";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
|
|
@ -21,9 +25,9 @@ type OnRowClick = (arg: {
|
||||||
row: any;
|
row: any;
|
||||||
rows: any[];
|
rows: any[];
|
||||||
idx: any;
|
idx: any;
|
||||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>;
|
event: React.MouseEvent<HTMLDivElement>;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
type SelectedRow = (arg: { row: any; rows: any[]; idx: any }) => boolean;
|
||||||
type TableListProp = {
|
type TableListProp = {
|
||||||
child: any;
|
child: any;
|
||||||
PassProp: any;
|
PassProp: any;
|
||||||
|
|
@ -34,28 +38,60 @@ type TableListProp = {
|
||||||
paging: { take: number; skip: number };
|
paging: { take: number; skip: number };
|
||||||
mode: "count" | "query";
|
mode: "count" | "query";
|
||||||
}) => Promise<any[]>;
|
}) => Promise<any[]>;
|
||||||
mode: "table" | "list" | "grid";
|
on_init: (arg?: any) => any;
|
||||||
|
mode: "table" | "list" | "grid" | "auto";
|
||||||
_meta: Record<string, any>;
|
_meta: Record<string, any>;
|
||||||
gen_fields: string[];
|
gen_fields: string[];
|
||||||
row_click: OnRowClick;
|
row_click: OnRowClick;
|
||||||
|
selected: SelectedRow;
|
||||||
id_parent?: string;
|
id_parent?: string;
|
||||||
|
feature?: Array<any>;
|
||||||
|
filter_name: string;
|
||||||
};
|
};
|
||||||
|
const w = window as any;
|
||||||
|
const selectCellClassname = css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
> input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const TableList: FC<TableListProp> = ({
|
export const TableList: FC<TableListProp> = ({
|
||||||
name,
|
name,
|
||||||
on_load,
|
on_load,
|
||||||
child,
|
child,
|
||||||
PassProp,
|
PassProp,
|
||||||
mode,
|
mode,
|
||||||
|
on_init,
|
||||||
_meta,
|
_meta,
|
||||||
gen_fields,
|
gen_fields,
|
||||||
row_click,
|
row_click,
|
||||||
|
selected,
|
||||||
id_parent,
|
id_parent,
|
||||||
|
feature,
|
||||||
|
filter_name,
|
||||||
}) => {
|
}) => {
|
||||||
|
console.log( `prasi_filter.${filter_name}`)
|
||||||
|
const where = get(w, `prasi_filter.${filter_name}`);
|
||||||
|
console.log(w.prasi_filter)
|
||||||
|
if (mode === "auto") {
|
||||||
|
if (w.isMobile) {
|
||||||
|
mode = "list";
|
||||||
|
} else {
|
||||||
|
mode = "table";
|
||||||
|
}
|
||||||
|
}
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
|
selectedRows: [] as {
|
||||||
|
pk: string | number;
|
||||||
|
rows: any;
|
||||||
|
}[],
|
||||||
el: null as null | HTMLDivElement,
|
el: null as null | HTMLDivElement,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
|
selectedRowIds: [] as (string | number)[],
|
||||||
rob: new ResizeObserver(([e]) => {
|
rob: new ResizeObserver(([e]) => {
|
||||||
local.height = e.contentRect.height;
|
local.height = e.contentRect.height;
|
||||||
local.width = e.contentRect.width;
|
local.width = e.contentRect.width;
|
||||||
|
|
@ -66,6 +102,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
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",
|
||||||
|
where: null as any,
|
||||||
paging: {
|
paging: {
|
||||||
take: 0,
|
take: 0,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
|
|
@ -134,10 +171,12 @@ export const TableList: FC<TableListProp> = ({
|
||||||
>,
|
>,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// code ini digunakan untuk mengambil nama dari pk yang akan digunakan sebagai key untuk id
|
||||||
|
const pk = local.pk?.name || "id";
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEditor) return;
|
if (isEditor) return;
|
||||||
(async () => {
|
(async () => {
|
||||||
|
on_init(local);
|
||||||
if (local.status === "reload" && typeof on_load === "function") {
|
if (local.status === "reload" && typeof on_load === "function") {
|
||||||
local.status = "loading";
|
local.status = "loading";
|
||||||
local.render();
|
local.render();
|
||||||
|
|
@ -145,13 +184,13 @@ export const TableList: FC<TableListProp> = ({
|
||||||
const orderBy = local.sort.orderBy || undefined;
|
const orderBy = local.sort.orderBy || undefined;
|
||||||
const load_args: any = {
|
const load_args: any = {
|
||||||
async reload() {},
|
async reload() {},
|
||||||
|
where,
|
||||||
orderBy,
|
orderBy,
|
||||||
paging: { take: local.paging.take, skip: local.paging.skip },
|
paging: { take: local.paging.take, skip: local.paging.skip },
|
||||||
};
|
};
|
||||||
if (id_parent) {
|
if (id_parent) {
|
||||||
load_args.paging = {};
|
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[]) => {
|
||||||
if (local.paging.skip === 0) {
|
if (local.paging.skip === 0) {
|
||||||
|
|
@ -172,11 +211,67 @@ export const TableList: FC<TableListProp> = ({
|
||||||
child,
|
child,
|
||||||
"props.meta.item.component.props.child.content.childs"
|
"props.meta.item.component.props.child.content.childs"
|
||||||
);
|
);
|
||||||
|
|
||||||
let childs: any[] = [];
|
let childs: any[] = [];
|
||||||
|
|
||||||
|
let checkedBox: any[] = [];
|
||||||
|
|
||||||
let sub_name = "fields";
|
let sub_name = "fields";
|
||||||
if (mode === "table") sub_name = "columns";
|
// if (mode === "table") sub_name = "columns";
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case "table":
|
||||||
|
sub_name = "tbl-col";
|
||||||
|
break;
|
||||||
|
case "list":
|
||||||
|
sub_name = "md-list";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// passing local data ke variable baru (biar gak panjang nulisnya hehe)
|
||||||
|
let rowData = local.data;
|
||||||
|
|
||||||
|
// function untuk menghandle checkbox di header (digunakan untuk check all)
|
||||||
|
const headerCheckboxClick = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
// jika checbox checked, maka semua rowData akan dimasukkan ke dalam local selected rows
|
||||||
|
rowData.forEach((data) => {
|
||||||
|
local.selectedRows.push({
|
||||||
|
pk: data[pk],
|
||||||
|
rows: data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
local.render();
|
||||||
|
} else {
|
||||||
|
// jika tidak, maka local selected rows akan dikosongkan
|
||||||
|
local.selectedRows = [];
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// function untuk menghandle checkbox pada setiap row (digunakan untuk check setiap rowData)
|
||||||
|
const checkboxClick = (rowId: any) => (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const checked = !!local.selectedRows.find((data) => data.pk === rowId);
|
||||||
|
console.log(checked);
|
||||||
|
if (!checked) {
|
||||||
|
// jika checkbox tercheck, maka rowData akan diambil jika memiliki id yang sama dengan rowId yang dikirim
|
||||||
|
const checkedRowData = rowData.filter((row) => row[pk] === rowId);
|
||||||
|
local.selectedRows.push({
|
||||||
|
pk: rowId,
|
||||||
|
rows: checkedRowData,
|
||||||
|
});
|
||||||
|
local.render();
|
||||||
|
console.log("selected", local.selectedRows);
|
||||||
|
} else {
|
||||||
|
// jika tidak, maka akan dihapus
|
||||||
|
local.selectedRows = local.selectedRows.filter(
|
||||||
|
(data) => data.pk !== rowId
|
||||||
|
);
|
||||||
|
local.render();
|
||||||
|
console.log("deselected", local.selectedRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const mode_child = raw_childs.find(
|
const mode_child = raw_childs.find(
|
||||||
(e: any) => e.name === sub_name || e.name === mode
|
(e: any) => e.name === sub_name || e.name === mode
|
||||||
|
|
@ -187,12 +282,57 @@ export const TableList: FC<TableListProp> = ({
|
||||||
childs = meta.item.childs;
|
childs = meta.item.childs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let columns: ColumnOrColumnGroup<any>[] = [];
|
||||||
const columns: ColumnOrColumnGroup<any>[] = [];
|
let isCheckbox = false;
|
||||||
|
try {
|
||||||
|
if (feature?.find((e) => e === "checkbox")) isCheckbox = true;
|
||||||
|
} catch (e) {}
|
||||||
|
if (childs.length && isCheckbox) {
|
||||||
|
columns.push({
|
||||||
|
key: SELECT_COLUMN_KEY,
|
||||||
|
name: "",
|
||||||
|
width: 35,
|
||||||
|
minWidth: 35,
|
||||||
|
maxWidth: 35,
|
||||||
|
resizable: false,
|
||||||
|
sortable: false,
|
||||||
|
frozen: true,
|
||||||
|
renderHeaderCell(props) {
|
||||||
|
return <input type="checkbox" onChange={headerCheckboxClick} />;
|
||||||
|
},
|
||||||
|
renderCell(props) {
|
||||||
|
// digunakan untuk mengecek apakah local selected rows memiliki pk dari props.row.id
|
||||||
|
const isChecked = local.selectedRows.some(
|
||||||
|
(checked) => checked.pk === props.row.id
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={checkboxClick(props.row.id)}
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`,
|
||||||
|
"c-flex c-items-center c-justify-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="c-pointer-events-none"
|
||||||
|
type="checkbox"
|
||||||
|
checked={isChecked}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
headerCellClass: selectCellClassname,
|
||||||
|
cellClass: selectCellClassname,
|
||||||
|
});
|
||||||
|
}
|
||||||
for (const child of childs) {
|
for (const child of childs) {
|
||||||
const key = getProp(child, "name", {});
|
const key = getProp(child, "name", {});
|
||||||
const name = getProp(child, "title", {});
|
const name = getProp(child, "title", {});
|
||||||
const width = parseInt(getProp(child, "width", {}));
|
const width = parseInt(getProp(child, "width", {}));
|
||||||
|
|
||||||
columns.push({
|
columns.push({
|
||||||
key,
|
key,
|
||||||
name,
|
name,
|
||||||
|
|
@ -217,6 +357,10 @@ export const TableList: FC<TableListProp> = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (mode === "list") {
|
||||||
|
// ambil satu index saja;
|
||||||
|
if (columns.length > 1) columns = columns.slice(0, 0 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (local.status === "resizing" && !isEditor) {
|
if (local.status === "resizing" && !isEditor) {
|
||||||
local.status = "ready";
|
local.status = "ready";
|
||||||
|
|
@ -264,6 +408,7 @@ export const TableList: FC<TableListProp> = ({
|
||||||
if (local.data.length === 0) {
|
if (local.data.length === 0) {
|
||||||
const load_args: any = {
|
const load_args: any = {
|
||||||
async reload() {},
|
async reload() {},
|
||||||
|
where,
|
||||||
paging: { take: local.paging.take, skip: local.paging.skip },
|
paging: { take: local.paging.take, skip: local.paging.skip },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -281,7 +426,6 @@ export const TableList: FC<TableListProp> = ({
|
||||||
if (id_parent && local.pk && local.sort.columns.length === 0) {
|
if (id_parent && local.pk && local.sort.columns.length === 0) {
|
||||||
data = sortTree(local.data, id_parent, local.pk.name);
|
data = sortTree(local.data, id_parent, local.pk.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "table") {
|
if (mode === "table") {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -342,7 +486,12 @@ export const TableList: FC<TableListProp> = ({
|
||||||
: {
|
: {
|
||||||
renderRow(key, props) {
|
renderRow(key, props) {
|
||||||
const is_selected = selected_idx === props.rowIdx;
|
const is_selected = selected_idx === props.rowIdx;
|
||||||
|
const isSelect = selected({
|
||||||
|
idx: props.rowIdx,
|
||||||
|
row: props.row,
|
||||||
|
rows: local.data,
|
||||||
|
});
|
||||||
|
// return "halo"
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
key={key}
|
key={key}
|
||||||
|
|
@ -360,10 +509,10 @@ export const TableList: FC<TableListProp> = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
isRowSelected={is_selected}
|
isRowSelected={true}
|
||||||
className={cx(
|
className={cx(
|
||||||
props.className,
|
props.className,
|
||||||
is_selected && "row-selected"
|
isSelect && "row-selected"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -390,6 +539,78 @@ export const TableList: FC<TableListProp> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (mode === "list") {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"c-w-full c-h-full c-flex-1 c-relative c-overflow-hidden",
|
||||||
|
dataGridStyle(local)
|
||||||
|
)}
|
||||||
|
ref={(el) => {
|
||||||
|
if (!local.el && el) {
|
||||||
|
local.el = el;
|
||||||
|
local.rob.observe(el);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="c-absolute c-inset-0">
|
||||||
|
{toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
|
||||||
|
{local.status === "init" ? (
|
||||||
|
<DataGrid
|
||||||
|
style={{ opacity: 0 }}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
key: "_",
|
||||||
|
name: "",
|
||||||
|
renderCell({ rowIdx }) {
|
||||||
|
if (local.paging.take < rowIdx) {
|
||||||
|
local.paging.take = rowIdx;
|
||||||
|
}
|
||||||
|
clearTimeout(local.paging.timeout);
|
||||||
|
local.paging.timeout = setTimeout(() => {
|
||||||
|
local.status = "reload";
|
||||||
|
local.paging.take = local.paging.take * 5;
|
||||||
|
local.render();
|
||||||
|
}, 100);
|
||||||
|
return <></>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rows={genRows(200)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="w-full h-full overflow-y-auto"
|
||||||
|
onScroll={local.paging.scroll}
|
||||||
|
>
|
||||||
|
{data.map((e, idx) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex-grow hover:c-bg-[#e2f1ff]"
|
||||||
|
onClick={(ev) => {
|
||||||
|
if (!isEditor && typeof row_click === "function") {
|
||||||
|
row_click({
|
||||||
|
event: ev,
|
||||||
|
idx: idx,
|
||||||
|
row: e,
|
||||||
|
rows: local.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PassProp idx={idx} row={e} col={{}} rows={local.data}>
|
||||||
|
{child}
|
||||||
|
</PassProp>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,21 +26,21 @@ export const MDAction: FC<{ md: MDLocal; PassProp: any; child: any }> = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"c-flex c-flex-row c-space-x-1 c-items-stretch c-self-stretch c-h-full"
|
"c-flex c-flex-row c-space-x-1 c-items-stretch c-self-stretch c-h-full"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{md.actions.map((e, idx) => {
|
{child}
|
||||||
|
{/* {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.action || e.label)) {
|
if (typeof e === "object" && (e.action || e.label)) {
|
||||||
return <PassProp item={e}>{child}</PassProp>;
|
return <PassProp item={e}>{child}</PassProp>;
|
||||||
}
|
}
|
||||||
})}
|
})} */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -62,7 +62,6 @@ const refreshActionInternal = (md: MDLocal, callback: () => void) => {
|
||||||
mode = md.tab.active;
|
mode = md.tab.active;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "master") {
|
if (mode === "master") {
|
||||||
const master_bread = getProp(md.master.internal, "actions", { md });
|
const master_bread = getProp(md.master.internal, "actions", { md });
|
||||||
if (master_bread instanceof Promise) {
|
if (master_bread instanceof Promise) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { MDLocal } from "./utils/typings";
|
||||||
import { BreadItem } from "../custom/Breadcrumb";
|
import { BreadItem } from "../custom/Breadcrumb";
|
||||||
|
|
||||||
export const refreshBread = (md: MDLocal) => {
|
export const refreshBread = (md: MDLocal) => {
|
||||||
const bread = useLocal({
|
const local = useLocal({
|
||||||
crumb: null as any,
|
crumb: null as any,
|
||||||
last_render: Date.now(),
|
last_render: Date.now(),
|
||||||
selected: null as any,
|
selected: null as any,
|
||||||
|
|
@ -12,16 +12,16 @@ export const refreshBread = (md: MDLocal) => {
|
||||||
|
|
||||||
if (isEditor) {
|
if (isEditor) {
|
||||||
refreshBreadInternal(md, () => {
|
refreshBreadInternal(md, () => {
|
||||||
if (Date.now() - bread.last_render > 1000) {
|
if (Date.now() - local.last_render > 1000) {
|
||||||
md.render();
|
md.render();
|
||||||
}
|
}
|
||||||
bread.last_render = Date.now();
|
local.last_render = Date.now();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (bread.crumb !== md.breadcrumb || md.selected !== bread.selected) {
|
if (local.crumb !== md.breadcrumb || md.selected !== local.selected) {
|
||||||
refreshBreadInternal(md, () => {
|
refreshBreadInternal(md, () => {
|
||||||
bread.crumb = md.breadcrumb;
|
local.crumb = md.breadcrumb;
|
||||||
bread.selected = md.selected;
|
local.selected = md.selected;
|
||||||
md.render();
|
md.render();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -29,8 +29,8 @@ export const refreshBread = (md: MDLocal) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshBreadInternal = (md: MDLocal, callback: () => void) => {
|
const refreshBreadInternal = (md: MDLocal, callback: () => void) => {
|
||||||
let mode = "master";
|
|
||||||
|
|
||||||
|
let mode = "master";
|
||||||
if (isEditor) {
|
if (isEditor) {
|
||||||
mode = md.props.editor_tab;
|
mode = md.props.editor_tab;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -41,11 +41,12 @@ const refreshBreadInternal = (md: MDLocal, callback: () => void) => {
|
||||||
mode = md.tab.active;
|
mode = md.tab.active;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "master") {
|
if (mode === "master") {
|
||||||
const master_bread = getProp(md.master.internal, "breadcrumb", { md });
|
const master_bread = getProp(md.master.internal, "breadcrumb", { md });
|
||||||
if (master_bread instanceof Promise) {
|
if (master_bread instanceof Promise) {
|
||||||
master_bread.then((e) => done(md, e, callback));
|
master_bread.then((e) => {
|
||||||
|
done(md, e, callback);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
done(md, master_bread, callback);
|
done(md, master_bread, callback);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,5 @@ 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 head = get(mdr.child, "props.meta.item.component.props.header.content");
|
const head = get(mdr.child, "props.meta.item.component.props.header.content");
|
||||||
const PassProp = mdr.PassProp;
|
const PassProp = mdr.PassProp;
|
||||||
|
|
||||||
return <PassProp md={md}>{head}</PassProp>;
|
return <PassProp md={md}>{head}</PassProp>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import { FC, Fragment, isValidElement } from "react";
|
||||||
|
import { getProp } from "./utils/get-prop";
|
||||||
|
import { MDActions, MDLocal } from "./utils/typings";
|
||||||
|
|
||||||
|
const w = window as unknown as {
|
||||||
|
md_panel_master: any;
|
||||||
|
};
|
||||||
|
export const MDMaster: FC<{ size: any; min_size: any; md: MDLocal;child: any; on_init: () => void }> = ({child,on_init, min_size,size, md
|
||||||
|
}) => {
|
||||||
|
let result = on_init();
|
||||||
|
|
||||||
|
let width = 0;
|
||||||
|
let min_width = 0;
|
||||||
|
try {
|
||||||
|
width = Number(size) || 0;
|
||||||
|
min_width = Number(min_size) || 0;
|
||||||
|
} catch (e: any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
w.md_panel_master = JSON.stringify({
|
||||||
|
size: width,
|
||||||
|
min_size: min_width
|
||||||
|
})
|
||||||
|
md.panel.min_size = min_width;
|
||||||
|
md.panel.size = width;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{child}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { MDLocal, MDRef } from "./utils/typings";
|
import { MDLocal, MDRef, w } from "./utils/typings";
|
||||||
import { MDHeader } from "./MDHeader";
|
import { MDHeader } from "./MDHeader";
|
||||||
|
|
||||||
export const should_show_tab = (md: MDLocal) => {
|
export const should_show_tab = (md: MDLocal) => {
|
||||||
|
|
@ -15,7 +15,6 @@ export const MDTab: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
if (!detail) {
|
if (!detail) {
|
||||||
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} />}
|
||||||
|
|
@ -68,7 +67,9 @@ export const MDNavTab: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
)}
|
)}
|
||||||
key={tab_name}
|
key={tab_name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isEditor) return;
|
if (isEditor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
md.tab.active = tab_name;
|
md.tab.active = tab_name;
|
||||||
md.params.apply();
|
md.params.apply();
|
||||||
md.render();
|
md.render();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
import { FC, useEffect, useRef } from "react";
|
import { FC, useEffect, useRef } from "react";
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
||||||
import { refreshBread } from "./MDBread";
|
import { refreshBread } from "./MDBread";
|
||||||
|
|
@ -9,11 +10,7 @@ import {
|
||||||
masterDetailParseHash as masterDetailParseParams,
|
masterDetailParseHash as masterDetailParseParams,
|
||||||
} from "./utils/md-hash";
|
} from "./utils/md-hash";
|
||||||
import { masterDetailInit, masterDetailSelected } from "./utils/md-init";
|
import { masterDetailInit, masterDetailSelected } from "./utils/md-init";
|
||||||
import { MDLocal, MDLocalInternal, MDRef } from "./utils/typings";
|
import { MDLocal, MDLocalInternal, MDRef, w } from "./utils/typings";
|
||||||
|
|
||||||
const w = window as unknown as {
|
|
||||||
generating_prasi_md: Record<string, true>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MasterDetail: FC<{
|
export const MasterDetail: FC<{
|
||||||
child: any;
|
child: any;
|
||||||
|
|
@ -26,7 +23,11 @@ export const MasterDetail: FC<{
|
||||||
gen_fields: any;
|
gen_fields: any;
|
||||||
gen_table: string;
|
gen_table: string;
|
||||||
on_init: (md: MDLocal) => void;
|
on_init: (md: MDLocal) => void;
|
||||||
|
_sync: any;
|
||||||
|
_item: any;
|
||||||
|
// _meta: any;
|
||||||
}> = ({
|
}> = ({
|
||||||
|
_item,
|
||||||
PassProp,
|
PassProp,
|
||||||
child,
|
child,
|
||||||
name,
|
name,
|
||||||
|
|
@ -39,13 +40,17 @@ export const MasterDetail: FC<{
|
||||||
on_init,
|
on_init,
|
||||||
}) => {
|
}) => {
|
||||||
let isGenerate = false as Boolean;
|
let isGenerate = false as Boolean;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!get(w.md_internal, _item.id)) w.md_internal = {
|
||||||
|
...w.md_internal,
|
||||||
|
[_item.id]: _item
|
||||||
|
};
|
||||||
isGenerate = false;
|
isGenerate = false;
|
||||||
if (w.generating_prasi_md["master_detail"]) {
|
if (w.generating_prasi_md["master_detail"]) {
|
||||||
isGenerate = true;
|
isGenerate = true;
|
||||||
}
|
}
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
|
|
||||||
const _ref = useRef({ PassProp, child });
|
const _ref = useRef({ PassProp, child });
|
||||||
const md = useLocal<MDLocalInternal>({
|
const md = useLocal<MDLocalInternal>({
|
||||||
name,
|
name,
|
||||||
|
|
@ -78,7 +83,13 @@ export const MasterDetail: FC<{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
master: { internal: null, render() {} },
|
master: { internal: null, render() {} },
|
||||||
|
panel: {
|
||||||
|
size: 25,
|
||||||
|
min_size: 0,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const local = useLocal({ init: false });
|
const local = useLocal({ init: false });
|
||||||
if (isEditor) {
|
if (isEditor) {
|
||||||
md.props.mode = mode;
|
md.props.mode = mode;
|
||||||
|
|
@ -98,7 +109,6 @@ export const MasterDetail: FC<{
|
||||||
}, [editor_tab]);
|
}, [editor_tab]);
|
||||||
|
|
||||||
refreshBread(md);
|
refreshBread(md);
|
||||||
|
|
||||||
if (!local.init || isEditor) {
|
if (!local.init || isEditor) {
|
||||||
local.init = true;
|
local.init = true;
|
||||||
masterDetailInit(md, child, editor_tab);
|
masterDetailInit(md, child, editor_tab);
|
||||||
|
|
@ -116,6 +126,7 @@ export const MasterDetail: FC<{
|
||||||
)}
|
)}
|
||||||
{md.props.mode === "full" && <ModeFull md={md} mdr={_ref.current} />}
|
{md.props.mode === "full" && <ModeFull md={md} mdr={_ref.current} />}
|
||||||
{md.props.mode === "v-split" && <ModeVSplit md={md} mdr={_ref.current} />}
|
{md.props.mode === "v-split" && <ModeVSplit md={md} mdr={_ref.current} />}
|
||||||
|
{md.props.mode === "h-split" && <ModeHSplit md={md} mdr={_ref.current} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -124,6 +135,7 @@ const ModeFull: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
if (should_show_tab(md)) {
|
if (should_show_tab(md)) {
|
||||||
return <MDTab md={md} mdr={mdr} />;
|
return <MDTab md={md} mdr={mdr} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!md.selected && <Master md={md} mdr={mdr} />}
|
{!md.selected && <Master md={md} mdr={mdr} />}
|
||||||
|
|
@ -136,7 +148,11 @@ const ModeVSplit: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
return (
|
return (
|
||||||
<div className={cx("c-flex-1")}>
|
<div className={cx("c-flex-1")}>
|
||||||
<PanelGroup direction="vertical">
|
<PanelGroup direction="vertical">
|
||||||
<Panel className="c-border-b">
|
<Panel
|
||||||
|
className="c-border-b"
|
||||||
|
defaultSize={md.panel.size}
|
||||||
|
minSize={md.panel.min_size}
|
||||||
|
>
|
||||||
<Master md={md} mdr={mdr} />
|
<Master md={md} mdr={mdr} />
|
||||||
</Panel>
|
</Panel>
|
||||||
<>
|
<>
|
||||||
|
|
@ -160,10 +176,40 @@ const ModeVSplit: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const ModeHSplit: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
|
return (
|
||||||
|
<div className={cx("c-flex-1")}>
|
||||||
|
<PanelGroup direction="horizontal">
|
||||||
|
<Panel
|
||||||
|
className="c-border-r"
|
||||||
|
defaultSize={md.panel.size}
|
||||||
|
minSize={md.panel.min_size}
|
||||||
|
>
|
||||||
|
<Master md={md} mdr={mdr} />
|
||||||
|
</Panel>
|
||||||
|
<>
|
||||||
|
<PanelResizeHandle />
|
||||||
|
<Panel
|
||||||
|
className="c-flex c-flex-col c-items-stretch c-w-10"
|
||||||
|
defaultSize={
|
||||||
|
parseInt(localStorage.getItem(`prasi-md-h-${md.name}`) || "") ||
|
||||||
|
undefined
|
||||||
|
}
|
||||||
|
onResize={(e) => {
|
||||||
|
if (e < 80) {
|
||||||
|
localStorage.setItem(`prasi-md-h-${md.name}`, e.toString());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MDTab md={md} mdr={mdr} />
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
</PanelGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
const Master: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
const Master: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||||
const PassProp = mdr.PassProp;
|
const PassProp = mdr.PassProp;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{md.props.show_head === "only-master" && <MDHeader md={md} mdr={mdr} />}
|
{md.props.show_head === "only-master" && <MDHeader md={md} mdr={mdr} />}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { MDLocal } from "./typings";
|
import { MDLocal, w } from "./typings";
|
||||||
import { getProp } from "./get-prop";
|
import { getProp } from "./get-prop";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { parseGenField } from "@/gen/utils";
|
import { parseGenField } from "@/gen/utils";
|
||||||
|
|
@ -12,6 +12,7 @@ export const masterDetailInit = (
|
||||||
child,
|
child,
|
||||||
"props.meta.item.component.props.child.content.childs"
|
"props.meta.item.component.props.child.content.childs"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Array.isArray(childs)) {
|
if (Array.isArray(childs)) {
|
||||||
md.master.internal = null;
|
md.master.internal = null;
|
||||||
md.childs = {};
|
md.childs = {};
|
||||||
|
|
@ -51,7 +52,6 @@ export const masterDetailInit = (
|
||||||
hide() {},
|
hide() {},
|
||||||
show() {},
|
show() {},
|
||||||
render() {},
|
render() {},
|
||||||
data: {},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
import { BreadItem } from "@/comps/custom/Breadcrumb";
|
import { BreadItem } from "@/comps/custom/Breadcrumb";
|
||||||
|
import { FMLocal } from "@/comps/form/typings";
|
||||||
import { GFCol } from "@/gen/utils";
|
import { GFCol } from "@/gen/utils";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
type ID_MASTER_DETAIL = string;
|
||||||
|
export const w = window as unknown as {
|
||||||
|
generating_prasi_md: Record<string, true>;
|
||||||
|
md_panel_master: any;
|
||||||
|
md_active_tab: Record<ID_MASTER_DETAIL, string>;
|
||||||
|
md_internal: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
export type MDActions = {
|
export type MDActions = {
|
||||||
action?: string;
|
action?: string;
|
||||||
label: ReactNode;
|
label: ReactNode;
|
||||||
|
|
@ -44,9 +53,15 @@ export type MDLocalInternal = {
|
||||||
show: () => void;
|
show: () => void;
|
||||||
render: () => void;
|
render: () => void;
|
||||||
internal: any;
|
internal: any;
|
||||||
data: any;
|
fm?: FMLocal;
|
||||||
|
md?: MDLocal;
|
||||||
|
list?: any;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
panel: {
|
||||||
|
size: number;
|
||||||
|
min_size: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
export type MDRef = { PassProp: any; child: any };
|
export type MDRef = { PassProp: any; child: any };
|
||||||
export type MDLocal = MDLocalInternal & { render: (force?: boolean) => void };
|
export type MDLocal = MDLocalInternal & { render: (force?: boolean) => void };
|
||||||
|
|
@ -108,6 +123,8 @@ export const MasterDetailType = `const md = {
|
||||||
render: () => void;
|
render: () => void;
|
||||||
internal: any;
|
internal: any;
|
||||||
data: any;
|
data: any;
|
||||||
|
fm?: fm;
|
||||||
|
md?: md;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};`;
|
};`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||||
|
|
||||||
|
import { cn } from "@/utils"
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
"c-peer c-inline-flex c-h-6 c-w-11 c-shrink-0 c-cursor-pointer c-items-center c-rounded-full c-border-2 c-border-transparent c-transition-colors focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2 focus-visible:c-ring-offset-background disabled:c-cursor-not-allowed disabled:c-opacity-50 data-[state=checked]:c-bg-primary data-[state=unchecked]:c-bg-input",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
"c-pointer-events-none c-block c-h-5 c-w-5 c-rounded-full c-bg-background c-shadow-lg c-ring-0 c-transition-transform data-[state=checked]:c-translate-x-5 data-[state=unchecked]:c-translate-x-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
))
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||||
|
|
||||||
|
export { Switch }
|
||||||
18
data.ts
18
data.ts
|
|
@ -1,10 +1,11 @@
|
||||||
export { FieldTypeCustom } from "@/comps/form/typings";
|
|
||||||
export { FieldTypeText } from "./comps/form/field/type/TypeText";
|
|
||||||
export { FieldTypeRelation } from "./comps/form/field/type/TypeRelation";
|
|
||||||
export { Form } from "@/comps/form/Form";
|
export { Form } from "@/comps/form/Form";
|
||||||
export { Field } from "@/comps/form/field/Field";
|
export { Field } from "@/comps/form/field/Field";
|
||||||
export { formType } from "@/comps/form/typings";
|
export {
|
||||||
export { fieldType } from "@/comps/form/typings";
|
FMLocal,
|
||||||
|
FieldTypeCustom,
|
||||||
|
fieldType,
|
||||||
|
formType,
|
||||||
|
} from "@/comps/form/typings";
|
||||||
export { TableList } from "@/comps/list/TableList";
|
export { TableList } from "@/comps/list/TableList";
|
||||||
export { TableListType } from "@/comps/list/typings";
|
export { TableListType } from "@/comps/list/typings";
|
||||||
export { MasterDetail } from "@/comps/md/MasterDetail";
|
export { MasterDetail } from "@/comps/md/MasterDetail";
|
||||||
|
|
@ -12,4 +13,11 @@ export { getProp } from "@/comps/md/utils/get-prop";
|
||||||
export { MasterDetailType } from "@/comps/md/utils/typings";
|
export { MasterDetailType } from "@/comps/md/utils/typings";
|
||||||
export { prasi_gen } from "@/gen/prasi_gen";
|
export { prasi_gen } from "@/gen/prasi_gen";
|
||||||
export { FormatValue } from "@/utils/format-value";
|
export { FormatValue } from "@/utils/format-value";
|
||||||
|
export { GetValue } from "@/utils/get-value";
|
||||||
|
export { FieldTypeRelation } from "./comps/form/field/type/TypeRelation";
|
||||||
|
export { FieldTypeSwitch } from "./comps/form/field/type/TypeSwitch";
|
||||||
|
export { FieldTypeText } from "./comps/form/field/type/TypeText";
|
||||||
export { MDAction } from "./comps/md/MDAction";
|
export { MDAction } from "./comps/md/MDAction";
|
||||||
|
export { MDMaster } from "./comps/md/MDMaster";
|
||||||
|
export { ImportExcel } from "./comps/list/ImportExcel";
|
||||||
|
export { ExportExcel } from "./comps/list/ExportExcel";
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,5 @@ export { Breadcrumb } from "./comps/custom/Breadcrumb";
|
||||||
export { Header } from "./comps/custom/Header";
|
export { Header } from "./comps/custom/Header";
|
||||||
export { Carousel } from "./comps/custom/Carousel";
|
export { Carousel } from "./comps/custom/Carousel";
|
||||||
export { Tree } from "./comps/list/Tree";
|
export { Tree } from "./comps/list/Tree";
|
||||||
|
export {MasterFilter} from "./comps/filter/Filter"
|
||||||
|
export * from "./data";
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { MDLocal } from "@/comps/md/utils/typings";
|
||||||
|
import { codeBuild } from "../master_detail/utils";
|
||||||
|
import { GenFn } from "../utils";
|
||||||
|
export const gen_action: GenFn<{ name: string; on_load: () => MDLocal }> = async (
|
||||||
|
modify,
|
||||||
|
data,
|
||||||
|
arg: { name: string, on_load: () => MDLocal }
|
||||||
|
) => {
|
||||||
|
const md = arg.on_load
|
||||||
|
console.log("halo gen")
|
||||||
|
console.log({modify, data, arg, md: md()})
|
||||||
|
// const res = await codeBuild({
|
||||||
|
// div: `\
|
||||||
|
// <>
|
||||||
|
// {md.tab.active === "${arg.name}" && (
|
||||||
|
// <div {...props} className={cx(props.className, "")}>
|
||||||
|
// {children}
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// </>
|
||||||
|
// `,
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import capitalize from "lodash.capitalize";
|
||||||
|
import { GFCol, createItem, parseGenField } from "../utils";
|
||||||
|
import { on_load } from "./on_load";
|
||||||
|
import { codeBuild, codeBuildTest } from "../master_detail/utils";
|
||||||
|
// import * as Excel from "exceljs";
|
||||||
|
import ExcelJS from "exceljs";
|
||||||
|
import * as FileSaver from "file-saver";
|
||||||
|
|
||||||
|
export const gen_export = async (
|
||||||
|
modify: (data: any) => void,
|
||||||
|
data: any,
|
||||||
|
arg: { mode: "table" | "list" | "grid" | "auto"; id_parent?: string }
|
||||||
|
) => {
|
||||||
|
const table = JSON.parse(data.gen_table.value) as string;
|
||||||
|
const raw_fields = JSON.parse(data.gen_fields.value) as (
|
||||||
|
| string
|
||||||
|
| { value: string; checked: string[] }
|
||||||
|
)[];
|
||||||
|
const select = {} as any;
|
||||||
|
let pk = "";
|
||||||
|
let pks: Record<string, string> = {};
|
||||||
|
|
||||||
|
const fields = parseGenField(raw_fields);
|
||||||
|
for (const f of fields) {
|
||||||
|
select[f.name] = true;
|
||||||
|
if (f.relation) {
|
||||||
|
select[f.name] = {
|
||||||
|
select: {},
|
||||||
|
};
|
||||||
|
for (const r of f.relation.fields) {
|
||||||
|
select[f.name].select[r.name] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (f.is_pk) {
|
||||||
|
pk = f.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.id_parent) {
|
||||||
|
select[arg.id_parent] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pk) {
|
||||||
|
alert("Failed to generate! Primary Key not found. ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const keys = Object.keys(select);
|
||||||
|
const tableName = data.gen_table.value;
|
||||||
|
const selectFields = keys.join(", ");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result: any[] = await db.$queryRawUnsafe(
|
||||||
|
`SELECT ${selectFields} FROM ${tableName};`
|
||||||
|
);
|
||||||
|
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
const worksheet = workbook.addWorksheet("Sheet 1");
|
||||||
|
|
||||||
|
const columns = Object.keys(result[0]);
|
||||||
|
worksheet.addRow(columns);
|
||||||
|
|
||||||
|
result.forEach((row) => {
|
||||||
|
const values = columns.map((col) => row[col]);
|
||||||
|
worksheet.addRow(values);
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
|
|
||||||
|
const blob = new Blob([buffer], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
FileSaver.saveAs(blob, "exported_data.xlsx");
|
||||||
|
|
||||||
|
console.log("Data exported");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error exporting data:", error);
|
||||||
|
} finally {
|
||||||
|
await db.$disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import capitalize from "lodash.capitalize";
|
import capitalize from "lodash.capitalize";
|
||||||
import { createItem, parseGenField } from "../utils";
|
import { GFCol, createItem, parseGenField } from "../utils";
|
||||||
import { on_load } from "./on_load";
|
import { on_load } from "./on_load";
|
||||||
import { codeBuild } from "../master_detail/utils";
|
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"; id_parent: string }
|
arg: { mode: "table" | "list" | "grid" | "auto"; 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 (
|
||||||
|
|
@ -29,7 +29,6 @@ export const gen_table_list = async (
|
||||||
select[f.name].select[r.name] = true;
|
select[f.name].select[r.name] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (f.is_pk) {
|
if (f.is_pk) {
|
||||||
pk = f.name;
|
pk = f.name;
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +55,14 @@ export const gen_table_list = async (
|
||||||
result["child"] = data["child"];
|
result["child"] = data["child"];
|
||||||
|
|
||||||
let sub_name = "fields";
|
let sub_name = "fields";
|
||||||
if (arg.mode === "table") sub_name = "columns";
|
switch (arg.mode) {
|
||||||
|
case "table":
|
||||||
|
sub_name = "tbl-col";
|
||||||
|
break;
|
||||||
|
case "list":
|
||||||
|
sub_name = "md-list";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
result["child"].content.childs = result["child"].content.childs.filter(
|
result["child"].content.childs = result["child"].content.childs.filter(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
|
|
@ -68,8 +74,11 @@ export const gen_table_list = async (
|
||||||
const child = createItem({
|
const child = createItem({
|
||||||
name: sub_name,
|
name: sub_name,
|
||||||
childs: fields
|
childs: fields
|
||||||
.map((e) => {
|
.map((e, idx) => {
|
||||||
if (e.is_pk) return;
|
if (idx >= 1 && arg.mode === "list") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.is_pk && arg.mode === "table") return;
|
||||||
let tree_depth = "";
|
let tree_depth = "";
|
||||||
let tree_depth_built = "";
|
let tree_depth_built = "";
|
||||||
if (first) {
|
if (first) {
|
||||||
|
|
@ -116,11 +125,28 @@ render(React.createElement("div", Object.assign({}, props, { className: cx(props
|
||||||
...result["child"].content.childs,
|
...result["child"].content.childs,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
// detect row yang aktif
|
||||||
if (data["selected"]) {
|
if (data["selected"]) {
|
||||||
result["selected"] = data["selected"];
|
result["selected"] = data["selected"];
|
||||||
result["selected"].value = `\
|
result["selected"].value = `\
|
||||||
({ row, rows, idx }: SelectedRow) => {
|
({ row, rows, idx }: SelectedRow) => {
|
||||||
|
try {
|
||||||
|
if (typeof md === "object") {
|
||||||
|
if (Array.isArray(md.selected)) {
|
||||||
|
if (md.selected.length) {
|
||||||
|
let select = md.selected.find((e) => e === row)
|
||||||
|
if(select) return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (md.selected === row) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SelectedRow = {
|
type SelectedRow = {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { GenMasterDetailArg, codeBuild } from "./utils";
|
||||||
|
import { createItem, formatName } from "../utils";
|
||||||
|
import { gen_form } from "../gen_form/gen_form";
|
||||||
|
|
||||||
|
export const genAtions = async (arg: GenMasterDetailArg, data: any) => {
|
||||||
|
console.log({ arg, data });
|
||||||
|
const header = get(data, "header");
|
||||||
|
const action_right = header.content.childs.find(
|
||||||
|
(e: any) => e.name === "right"
|
||||||
|
);
|
||||||
|
|
||||||
|
const list_action_right =
|
||||||
|
typeof action_right === "object" ? action_right.childs : [];
|
||||||
|
for (const c of get(data, "child.content.childs") || []) {
|
||||||
|
if (c.component?.id === "cb52075a-14ab-455a-9847-6f1d929a2a73") {
|
||||||
|
const name_tab = c.component.props.name.value.replaceAll('"', "");
|
||||||
|
const is_already = list_action_right.length
|
||||||
|
? list_action_right.some(
|
||||||
|
(action: any) =>
|
||||||
|
action.component.props.name.value.replaceAll(/`/g, "") ===
|
||||||
|
name_tab
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
if (!is_already) {
|
||||||
|
const res = await codeBuild({
|
||||||
|
show: `\
|
||||||
|
() => {
|
||||||
|
if (typeof md === "object") {
|
||||||
|
const tab_active = md.tab.active;
|
||||||
|
if (name === "master") {
|
||||||
|
if (tab_active === "" || tab_active === "master") return true;
|
||||||
|
} else {
|
||||||
|
if (tab_active === name) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
// const childs = get(action_right, "childs") || [];
|
||||||
|
// childs.push(
|
||||||
|
// createItem({
|
||||||
|
// name: "action"
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// const childs = get(data, "child.content.childs[3].childs") || [];
|
||||||
|
// console.log(childs)
|
||||||
|
// childs.push(
|
||||||
|
// createItem({
|
||||||
|
// name: "action",
|
||||||
|
// // component: {
|
||||||
|
// // id: "83a2859d-2f72-4e7d-a0c6-9d3368e1ed85",
|
||||||
|
// // props: {
|
||||||
|
// // show: res.show,
|
||||||
|
// // name: name_tab,
|
||||||
|
// // },
|
||||||
|
// // },
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// console.log({action_right})
|
||||||
|
}
|
||||||
|
console.log(is_already);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -4,8 +4,21 @@ import { createItem, formatName } from "../utils";
|
||||||
import { gen_form } from "../gen_form/gen_form";
|
import { gen_form } from "../gen_form/gen_form";
|
||||||
|
|
||||||
export const genForm = async (arg: GenMasterDetailArg, data: any) => {
|
export const genForm = async (arg: GenMasterDetailArg, data: any) => {
|
||||||
|
// tambahi kondisi, jika gk nemu component tab detail maka update tab detail atau bikin baru
|
||||||
|
const childs_form = data.child.content.childs || [];
|
||||||
|
const tab_form = childs_form.filter((c: any) => c.component?.id === "cb52075a-14ab-455a-9847-6f1d929a2a73" && c.component.props.name.value.replaceAll("\"","") === "detail")
|
||||||
for (const c of get(data, "child.content.childs") || []) {
|
for (const c of get(data, "child.content.childs") || []) {
|
||||||
if (c.component?.id === "cb52075a-14ab-455a-9847-6f1d929a2a73") {
|
if (c.component?.id === "cb52075a-14ab-455a-9847-6f1d929a2a73") {
|
||||||
|
// passing khusus tab detail, biar tab lain gk di rewrite.
|
||||||
|
let passing = false;
|
||||||
|
if(!tab_form.length){
|
||||||
|
passing = true;
|
||||||
|
}else{
|
||||||
|
const name_tab = c.component.props.name.value.replaceAll("\"","");
|
||||||
|
passing = name_tab === "detail"
|
||||||
|
}
|
||||||
|
if(passing){
|
||||||
|
|
||||||
const res = await codeBuild({
|
const res = await codeBuild({
|
||||||
breadcrumb: `\
|
breadcrumb: `\
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -14,6 +27,7 @@ async () => {
|
||||||
label: "List ${formatName(arg.gen_table)}",
|
label: "List ${formatName(arg.gen_table)}",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
md.selected = null;
|
md.selected = null;
|
||||||
|
md.tab.active = "";
|
||||||
md.internal.action_should_refresh = true;
|
md.internal.action_should_refresh = true;
|
||||||
md.params.apply();
|
md.params.apply();
|
||||||
md.render();
|
md.render();
|
||||||
|
|
@ -65,27 +79,36 @@ type ActionItem =
|
||||||
onClick?: (e: any) => Promise<void>;
|
onClick?: (e: any) => Promise<void>;
|
||||||
}
|
}
|
||||||
| React.ReactNode`,
|
| React.ReactNode`,
|
||||||
on_init: `\
|
// on_init: `\
|
||||||
async ({ submit, reload }: Init) => {
|
// async ({ submit, reload }: Init) => {
|
||||||
const tab = md.childs[md.tab.active];
|
// const tab = md.childs[md.tab.active];
|
||||||
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
|
// const save_btn = actions
|
||||||
.filter((e) => e)
|
// .filter((e) => e)
|
||||||
.find((e) => e.action === "save");
|
// .find((e) => e.action === "save");
|
||||||
if (save_btn) {
|
// if (save_btn) {
|
||||||
save_btn.onClick = async () => {
|
// save_btn.onClick = async () => {
|
||||||
await submit();
|
// await submit();
|
||||||
};
|
// };
|
||||||
md.actions = actions;
|
// md.actions = actions;
|
||||||
md.render();
|
// md.render();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
type Init = { submit: () => Promise<boolean>; reload: () => void }
|
// type Init = { submit: () => Promise<boolean>; reload: () => void }
|
||||||
|
// `,
|
||||||
|
on_init: `\
|
||||||
|
async ({ submit, reload, fm }: Init) => {
|
||||||
|
if (typeof md === "object") {
|
||||||
|
md.childs["form"] = {} as any;
|
||||||
|
md.childs["form"]["fm"] = fm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type Init = { submit: () => Promise<boolean>; reload: () => void; fm:any }
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
const comp = createItem({
|
const comp = createItem({
|
||||||
|
|
@ -102,7 +125,6 @@ type Init = { submit: () => Promise<boolean>; reload: () => void }
|
||||||
for (const [k, v] of Object.entries(comp.component.props)) {
|
for (const [k, v] of Object.entries(comp.component.props)) {
|
||||||
c.component.props[k] = v;
|
c.component.props[k] = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
const childs = get(c, "component.props.child.content.childs") || [];
|
const childs = get(c, "component.props.child.content.childs") || [];
|
||||||
childs.length = 0;
|
childs.length = 0;
|
||||||
childs.push(
|
childs.push(
|
||||||
|
|
@ -131,4 +153,5 @@ type Init = { submit: () => Promise<boolean>; reload: () => void }
|
||||||
await gen_form(modify, data);
|
await gen_form(modify, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { gen_table_list } from "../gen_table_list/gen_table_list";
|
||||||
export const genList = async (arg: GenMasterDetailArg, data: any) => {
|
export const genList = async (arg: GenMasterDetailArg, data: any) => {
|
||||||
for (const c of get(data, "child.content.childs") || []) {
|
for (const c of get(data, "child.content.childs") || []) {
|
||||||
if (c.component?.id === "c68415ca-dac5-44fe-aeb6-936caf8cc491") {
|
if (c.component?.id === "c68415ca-dac5-44fe-aeb6-936caf8cc491") {
|
||||||
|
|
||||||
const res = await codeBuild({
|
const res = await codeBuild({
|
||||||
row_click: `\
|
row_click: `\
|
||||||
({ row, rows, idx, event }: OnRowClick) => {
|
({ row, rows, idx, event }: OnRowClick) => {
|
||||||
|
|
@ -21,6 +22,32 @@ type OnRowClick = {
|
||||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>;
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
selected: `\
|
||||||
|
({ row, rows, idx }: SelectedRow) => {
|
||||||
|
try {
|
||||||
|
if (typeof md === "object") {
|
||||||
|
if (Array.isArray(md.selected)) {
|
||||||
|
if (md.selected.length) {
|
||||||
|
let select = md.selected.find((e) => e === row)
|
||||||
|
if(select) return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (md.selected === row) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SelectedRow = {
|
||||||
|
row: any;
|
||||||
|
rows: any[];
|
||||||
|
idx: any;
|
||||||
|
}`,
|
||||||
breadcrumb: `\
|
breadcrumb: `\
|
||||||
async () => {
|
async () => {
|
||||||
return [{ label: "List ${formatName(arg.gen_table)}" }] as BreadItem[];
|
return [{ label: "List ${formatName(arg.gen_table)}" }] as BreadItem[];
|
||||||
|
|
@ -75,9 +102,9 @@ type ActionItem =
|
||||||
name: arg.gen_table,
|
name: arg.gen_table,
|
||||||
gen_table: arg.gen_table,
|
gen_table: arg.gen_table,
|
||||||
generate: "y",
|
generate: "y",
|
||||||
selected: "",
|
|
||||||
on_load: "",
|
on_load: "",
|
||||||
row_click: res.row_click,
|
row_click: res.row_click,
|
||||||
|
selected: res.selected,
|
||||||
gen_fields: [JSON.stringify(arg.gen_fields)],
|
gen_fields: [JSON.stringify(arg.gen_fields)],
|
||||||
child: {
|
child: {
|
||||||
childs: [],
|
childs: [],
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { PropOptRaw, GenFn, parseGenField, parseOpt } from "../utils";
|
import { PropOptRaw, GenFn, parseGenField, parseOpt } from "../utils";
|
||||||
|
import { genAtions } from "./gen-action";
|
||||||
import { genForm } from "./gen-form";
|
import { genForm } from "./gen-form";
|
||||||
import { genList } from "./gen-list";
|
import { genList } from "./gen-list";
|
||||||
import { GenMasterDetailArg } from "./utils";
|
import { GenMasterDetailArg } from "./utils";
|
||||||
|
|
@ -35,6 +36,4 @@ export const gen_master_detail: GenFn<GenMasterDetailArg> = async (
|
||||||
delete w.generating_prasi_md["master_detail"];
|
delete w.generating_prasi_md["master_detail"];
|
||||||
modify(result);
|
modify(result);
|
||||||
|
|
||||||
console.log("LAGI GENERATE");
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export type GenMasterDetailArg = {
|
||||||
gen_feature: PropOptRaw;
|
gen_feature: PropOptRaw;
|
||||||
gen_table: string;
|
gen_table: string;
|
||||||
gen_fields: PropOptRaw;
|
gen_fields: PropOptRaw;
|
||||||
|
child:any
|
||||||
};
|
};
|
||||||
|
|
||||||
export const codeBuild = async <K extends Record<string, string>>(input: K) => {
|
export const codeBuild = async <K extends Record<string, string>>(input: K) => {
|
||||||
|
|
@ -22,3 +23,18 @@ export const codeBuild = async <K extends Record<string, string>>(input: K) => {
|
||||||
|
|
||||||
return result as Record<keyof K, [string, string]>;
|
return result as Record<keyof K, [string, string]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const codeBuildTest = async (input: string) => {
|
||||||
|
const result = {} as any;
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
const res = await _api.code_build(input);
|
||||||
|
return res;
|
||||||
|
// if (res) {
|
||||||
|
// for (const [k, v] of Object.entries(res) as any) {
|
||||||
|
// result[k] = [input[k], v];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return result as Record<keyof K, [string, string]>;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
|
import { gen_action } from "./gen_action/gen_action";
|
||||||
import { gen_form } from "./gen_form/gen_form";
|
import { gen_form } from "./gen_form/gen_form";
|
||||||
import { gen_relation } from "./gen_relation/gen_relation";
|
import { gen_relation } from "./gen_relation/gen_relation";
|
||||||
import { gen_table_list } from "./gen_table_list/gen_table_list";
|
import { gen_table_list } from "./gen_table_list/gen_table_list";
|
||||||
|
import { gen_export } from "./gen_table_list/gen_export";
|
||||||
import { gen_master_detail } from "./master_detail/gen";
|
import { gen_master_detail } from "./master_detail/gen";
|
||||||
import { gen_prop_fields } from "./prop/gen_prop_fields";
|
import { gen_prop_fields } from "./prop/gen_prop_fields";
|
||||||
import { gen_props_table } from "./prop/gen_prop_table";
|
import { gen_props_table } from "./prop/gen_prop_table";
|
||||||
|
|
||||||
export const prasi_gen = {
|
export const prasi_gen = {
|
||||||
|
actions_tab: gen_action,
|
||||||
master_detail: gen_master_detail,
|
master_detail: gen_master_detail,
|
||||||
table_list: gen_table_list,
|
table_list: gen_table_list,
|
||||||
|
export_excel: gen_export,
|
||||||
form: gen_form,
|
form: gen_form,
|
||||||
relation: gen_relation,
|
relation: gen_relation,
|
||||||
prop: {
|
prop: {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ export const gen_prop_fields = async (gen_table: string) => {
|
||||||
id_site = window.location.hostname;
|
id_site = window.location.hostname;
|
||||||
}
|
}
|
||||||
const schema = getSchemaOnStorage(id_site, gen_table);
|
const schema = getSchemaOnStorage(id_site, gen_table);
|
||||||
console.log({schema})
|
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
const result: {
|
const result: {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -155,13 +154,12 @@ export const gen_prop_fields = async (gen_table: string) => {
|
||||||
|
|
||||||
const saveSchemaOnStorage = (res: any, id_site: string, table: string) => {
|
const saveSchemaOnStorage = (res: any, id_site: string, table: string) => {
|
||||||
let schemaSite = null;
|
let schemaSite = null;
|
||||||
let schema_master_detail: Record<string, any> = {}
|
let schema_master_detail: Record<string, any> = {};
|
||||||
const keys = `schema-md-${id_site}`
|
const keys = `schema-md-${id_site}`;
|
||||||
try {
|
try {
|
||||||
let smd = localStorage.getItem(keys) as string;
|
let smd = localStorage.getItem(keys) as string;
|
||||||
schemaSite = JSON.parse(smd);
|
schemaSite = JSON.parse(smd);
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
schema_master_detail = {
|
schema_master_detail = {
|
||||||
...schemaSite,
|
...schemaSite,
|
||||||
|
|
@ -171,16 +169,15 @@ const saveSchemaOnStorage = (res: any, id_site: string, table: string) => {
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e.message);
|
console.error(e.message);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const getSchemaOnStorage = (id_site: string, table: string) => {
|
const getSchemaOnStorage = (id_site: string, table: string) => {
|
||||||
const keys = `schema-md-${id_site}`
|
const keys = `schema-md-${id_site}`;
|
||||||
let schemaSite = null;
|
let schemaSite = null;
|
||||||
try {
|
try {
|
||||||
let smd = localStorage.getItem(keys) as string;
|
let smd = localStorage.getItem(keys) as string;
|
||||||
schemaSite = JSON.parse(smd);
|
schemaSite = JSON.parse(smd);
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
}
|
|
||||||
const schema = get(schemaSite, `${table}`);
|
const schema = get(schemaSite, `${table}`);
|
||||||
if(schema) return JSON.parse(schema);
|
if (schema) return JSON.parse(schema);
|
||||||
return null
|
return null;
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@ const cache: any = [];
|
||||||
|
|
||||||
export const gen_props_table = async () => {
|
export const gen_props_table = async () => {
|
||||||
if (cache.length > 0) return cache;
|
if (cache.length > 0) return cache;
|
||||||
|
|
||||||
const tables = await db._schema.tables();
|
const tables = await db._schema.tables();
|
||||||
if (!Array.isArray(tables)) {
|
if (!Array.isArray(tables)) {
|
||||||
alert("WARNING: failed to get tables from app server");
|
alert("WARNING: failed to get tables from app server");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = [{ value: "", label: "" }];
|
const result = [{ value: "", label: "" }];
|
||||||
const final = [
|
const final = [
|
||||||
...result,
|
...result,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ export const FormatValue: FC<{
|
||||||
tree_depth?: number;
|
tree_depth?: number;
|
||||||
}> = (prop) => {
|
}> = (prop) => {
|
||||||
const { value, gen_fields, name, tree_depth } = prop;
|
const { value, gen_fields, name, tree_depth } = 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)) {
|
||||||
|
|
@ -22,7 +21,16 @@ export const FormatValue: FC<{
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...JSON.parse(e.value),
|
...JSON.parse(e.value),
|
||||||
checked: e.checked.map(JSON.parse),
|
checked: e.checked.map((ex: any) => {
|
||||||
|
if(typeof ex === "string"){
|
||||||
|
return JSON.parse(e.value)
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
return JSON.parse(ex["value"]);
|
||||||
|
}catch(em){
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { GFCol } from "@/gen/utils";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
export const fields_map = new Map<string, (GFCol & { checked?: GFCol[] })[]>();
|
||||||
|
|
||||||
|
export const GetValue: FC<{
|
||||||
|
value: any;
|
||||||
|
name: string;
|
||||||
|
}> = (prop) => {
|
||||||
|
const { value, name } = prop;
|
||||||
|
return get(value, name)
|
||||||
|
return (
|
||||||
|
<div className="c-flex c-space-x-2 c-items-center">
|
||||||
|
{get(value, name)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
// if (gen_fields) {
|
||||||
|
// const gf = JSON.stringify(gen_fields);
|
||||||
|
// if (!fields_map.has(gf)) {
|
||||||
|
// fields_map.set(
|
||||||
|
// gf,
|
||||||
|
// gen_fields.map((e: any) => {
|
||||||
|
// if (typeof e === "string") {
|
||||||
|
// return JSON.parse(e);
|
||||||
|
// } else {
|
||||||
|
// return {
|
||||||
|
// ...JSON.parse(e.value),
|
||||||
|
// checked: e.checked.map(JSON.parse),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const fields = fields_map.get(gf);
|
||||||
|
|
||||||
|
// if (typeof value === "object" && value) {
|
||||||
|
// const rel = fields?.find((e) => e.name === name);
|
||||||
|
// if (rel && rel.checked) {
|
||||||
|
// const result = rel.checked
|
||||||
|
// .filter((e) => !e.is_pk)
|
||||||
|
// .map((e) => {
|
||||||
|
// return value[e.name];
|
||||||
|
// })
|
||||||
|
// .join(" - ");
|
||||||
|
|
||||||
|
// if (Array.isArray(value)) {
|
||||||
|
// const len = value.length;
|
||||||
|
// if (len === 0) return ` - `;
|
||||||
|
// return `${len} item${len > 1 ? "s" : ""}`;
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return JSON.stringify(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