This commit is contained in:
rizrmd 2024-06-12 11:16:13 -07:00
parent a07251c76e
commit fff9049ff6
51 changed files with 1174 additions and 943 deletions

View File

@ -2,6 +2,7 @@ import { useLocal } from "@/utils/use-local";
import { FC, ReactNode, useEffect } from "react";
import { Skeleton } from "../ui/skeleton";
import get from "lodash.get";
import { FieldLoading } from "../ui/field-loading";
export type BreadItem = {
label: React.ReactNode;
@ -16,43 +17,7 @@ type BreadcrumbProps = {
item?: PrasiItem;
};
export const Breadcrumb: FC<BreadcrumbProps> = ({
value,
className,
on_load,
item,
}) => {
const local = useLocal({
list: [] as BreadItem[],
status: "init" as "init" | "loading" | "ready",
params: {},
reload: () => {
if (typeof on_load === "function") {
local.status = "loading";
on_load(local).then((res: any) => {
if (typeof res === "object") {
if (!Array.isArray(res)) {
local.list = get(res, "list") || [];
} else {
local.list = res;
}
}
local.status = "ready";
local.render();
});
}
local.render();
},
});
useEffect(() => {
if (value) {
local.list = value;
local.status = "ready";
}
local.reload();
}, []);
export const Breadcrumb: FC<BreadcrumbProps> = ({ value, className }) => {
return (
<div
className={cx(
@ -60,55 +25,49 @@ export const Breadcrumb: FC<BreadcrumbProps> = ({
className
)}
>
{local.status !== "ready" ? (
<Skeleton className="c-h-4 c-w-[80%]" />
) : (
<>
{local.list &&
(local.list || []).map((item, index): ReactNode => {
const lastIndex = local.list.length - 1;
{(value || []).map((cur, index): ReactNode => {
const lastIndex = (value || []).length - 1;
return (
<>
{index === lastIndex ? (
<h1 className="c-font-semibold c-text-xs md:c-text-base">
{item?.label}
</h1>
) : (
<h1
className="c-font-normal c-text-xs md:c-text-base hover:c-cursor-pointer hover:c-underline"
onClick={(ev) => {
if (item.url) navigate(item.url || "");
if (item.onClick) item.onClick(ev);
}}
>
{item?.label}
</h1>
)}
return (
<>
{index === lastIndex ? (
<h1 className="c-font-semibold c-text-xs md:c-text-base">
{cur?.label}
</h1>
) : (
<h1
className="c-font-normal c-text-xs md:c-text-base hover:c-cursor-pointer hover:c-underline"
onClick={(ev) => {
if (isEditor) return;
if (cur.url) navigate(cur.url || "");
if (cur.onClick) cur.onClick(ev);
}}
>
{cur?.label}
</h1>
)}
{index !== lastIndex && (
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-chevron-right"
>
<path d="m9 18 6-6-6-6" />
</svg>
</div>
)}
</>
);
})}
</>
)}
{index !== lastIndex && (
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-chevron-right"
>
<path d="m9 18 6-6-6-6" />
</svg>
</div>
)}
</>
);
})}
</div>
);
};

View File

@ -7,13 +7,14 @@ import { editorFormData } from "./utils/ed-data";
import { formInit } from "./utils/init";
import { formReload } from "./utils/reload";
import { getPathname } from "lib/utils/pathname";
import { sofDeleteField } from "lib/utils/soft-del-rel";
const editorFormWidth = {} as Record<string, { w: number; f: any }>;
export { FMLocal } from "./typings";
export const Form: FC<FMProps> = (props) => {
const { PassProp, body } = props;
const { PassProp, body, feature, sfd_field } = props;
const fm = useLocal<FMInternal>({
data: editorFormData[props.item.id]
? editorFormData[props.item.id].data
@ -51,8 +52,30 @@ export const Form: FC<FMProps> = (props) => {
? editorFormWidth[props.item.id].f
: "full",
},
soft_delete: {
field: null
}
});
useEffect(() => {
// deteksi jika ada softdelete
if(Array.isArray(props.feature)){
if(props.feature?.find((e) => e === "soft_delete")){
const result = sofDeleteField(props.gen_table, sfd_field)
if (result instanceof Promise) {
result.then((e) => {
// simpan fields yang berisi name dan type fields soft delete
fm.soft_delete.field = e;
if(!isEditor){
fm.render();
}
});
}
}else{
fm.soft_delete.field = null;
}
}
}, [])
const ref = useRef({
el: null as null | HTMLFormElement,
rob: new ResizeObserver(([e]) => {

View File

@ -31,45 +31,7 @@ export const Field: FC<FieldProp> = (arg) => {
const errors = fm.error.get(name);
const props = { ...arg.props };
delete props.className;
if (type === "-" || !type || sub_type === "-" || !sub_type) {
return (
<>
<div className="c-p-4">
Field {arg.label} is not ready
<br />
<div
className={css`
font-size: 12px;
font-weight: normal;
`}
>
{arg.msg_error}
</div>
</div>
</>
);
}
if (
(type === "multi-option" && sub_type === "-") ||
(type === "multi-option" && sub_type === "table-edit" && (!arg.gen_table || arg.gen_table === ""))
) {
return (
<>
<div className="c-p-4">
Table Edit {arg.label} is not ready
<br />
<div
className={css`
font-size: 12px;
font-weight: normal;
`}
>
{arg.msg_error}
</div>
</div>
</>
);
}
return (
<label
@ -79,8 +41,8 @@ export const Field: FC<FieldProp> = (arg) => {
css`
padding: 5px 0px 0px 10px;
`,
w === "auto" && fm.size.field === "full" && "c-w-full",
w === "auto" && fm.size.field === "half" && "c-w-1/2",
w === "full" && "c-w-full",
w === "¾" && "c-w-3/4",
w === "½" && "c-w-1/2",
@ -111,7 +73,7 @@ export const Field: FC<FieldProp> = (arg) => {
{errors.length > 0 && (
<div
className={cx(
"c-p-2 c-text-xs c-text-red-600",
"field-error c-p-2 c-text-xs c-text-red-600",
field.desc && "c-pt-0"
)}
>

View File

@ -18,7 +18,7 @@ export const FieldInput: FC<{
child: any;
_item: PrasiItem;
arg: FieldProp;
}> = ({ field, fm, arg, _item, PassProp, child}) => {
}> = ({ field, fm, arg, _item, PassProp, child }) => {
// return <></>
const prefix =
typeof field.prefix === "function"
@ -48,6 +48,8 @@ export const FieldInput: FC<{
}
}
}
let table_edit = null;
if (type_field === "multi-option" && arg.sub_type === "table-edit") {
const childsTableEdit = get(
_item,
@ -57,7 +59,7 @@ export const FieldInput: FC<{
child: get(_item, "edit.props.child.value") as PrasiItem,
bottom: childsTableEdit.find((e) => e.name === "bottom") as PrasiItem,
};
return (
table_edit = (
<TableEdit
on_init={() => {
return fm;
@ -70,13 +72,38 @@ export const FieldInput: FC<{
body={tableEdit.child}
/>
);
return <>{arg.child}</>;
}
let not_ready: any = false;
if (
arg.type === "multi-option" &&
arg.sub_type === "table-edit" &&
(!arg.gen_fields?.length || arg.gen_fields?.length === 1)
) {
not_ready = (
<>
<div className="c-m-1 c-p-2 c-border c-border-red-500">
Field: {arg.label} is not ready
<br />
<div
className={css`
font-size: 12px;
font-weight: normal;
white-space: pre;
`}
>
Please select fields in relation and click generate.
</div>
</div>
</>
);
}
return (
<div
className={cx(
!["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"
? "field-outer c-flex c-flex-1 c-flex-row c-rounded c-border c-text-sm c-bg-white"
: "",
fm.status === "loading"
? css`
@ -115,30 +142,40 @@ export const FieldInput: FC<{
field.disabled && "c-pointer-events-none"
)}
>
{type_field === "custom" && arg.custom ? (
<>{custom}</>
{not_ready ? (
not_ready
) : (
<>
{["date", "input"].includes(type_field) ? (
<FieldTypeInput
field={field}
fm={fm}
arg={arg}
prop={
{
type: type_field as any,
sub_type: arg.sub_type,
prefix,
suffix,
} as PropTypeInput
}
/>
) : ["single-option"].includes(type_field) ? (
<SingleOption arg={arg} field={field} fm={fm} />
) : ["multi-option"].includes(type_field) ? (
<MultiOption arg={arg} field={field} fm={fm} />
{type_field === "custom" && arg.custom ? (
<>{custom}</>
) : (
<>{isValidElement(type_field) && type_field}</>
<>
{["date", "input"].includes(type_field) ? (
<FieldTypeInput
field={field}
fm={fm}
arg={arg}
prop={
{
type: type_field as any,
sub_type: arg.sub_type,
prefix,
suffix,
} as PropTypeInput
}
/>
) : ["single-option"].includes(type_field) ? (
<SingleOption arg={arg} field={field} fm={fm} />
) : ["multi-option"].includes(type_field) ? (
arg.sub_type === "table-edit" ? (
table_edit
) : (
<MultiOption arg={arg} field={field} fm={fm} />
)
) : (
<>{isValidElement(type_field) && type_field}</>
)}
</>
)}
</>
)}

View File

@ -1,9 +1,8 @@
import { TableList } from "lib/comps/list/TableList";
import { useLocal } from "lib/utils/use-local";
import { FC } from "react";
import { FC, ReactElement, useEffect, useRef } from "react";
import { BaseForm } from "../../base/BaseForm";
import { FMLocal } from "../../typings";
import get from "lodash.get";
export const TableEdit: FC<{
on_init: () => FMLocal;
@ -21,7 +20,17 @@ export const TableEdit: FC<{
},
() => {}
);
const value = isEditor ? [{}] :Array.isArray(fm.data[name]) ? fm.data[name] : [];
const ref = useRef<HTMLDivElement>(null);
if (!Array.isArray(fm.data[name])) {
if (typeof fm.data[name] === "object") {
fm.data[name] = [JSON.parse(JSON.stringify(fm.data[name]))];
} else {
fm.data[name] = [];
}
}
const value = fm.data[name];
return (
<>
<div className="c-w-full c-h-full c-flex c-flex-col">
@ -32,6 +41,18 @@ export const TableEdit: FC<{
> .rdg {
overflow-y: hidden !important;
}
.rdg-cell > div {
flex-direction: row;
align-items: center;
padding-right: 5px;
.field {
flex: 1;
padding-top: 0px;
}
}
.field-error {
display: none;
}
`
)}
style={{
@ -41,57 +62,82 @@ export const TableEdit: FC<{
? 50
: value.length * 50 + 50,
}}
ref={ref}
>
{/* <button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
db.milestone.create({
data: {
goal_setting: {
connect: {
id: "456222b4-4c0c-4f53-b62e-3e1f685f9c4a",
},
},
name: "124124",
goal: {
connect: {
id: "9fa14b83-2d8b-4c3d-a18c-c17d99a37813",
},
},
},
});
}}
>
COBA
</button> */}
<TableList
rowHeight={50}
row_height={50}
feature={[]}
child={child}
PassProp={PassProp}
name={""}
value={value}
on_init={() => {}}
on_init={(tbl) => {
local.tbl = tbl;
local.render();
}}
mode={"table"}
_item={item}
gen_fields={[]}
row_click={() => {}}
row_click={({ event }) => {
event.preventDefault();
event.stopPropagation();
}}
selected={() => {
return false;
}}
filter_name={""}
render_col={(arg: any) => {
render_col={(arg) => {
const { props, tbl, child } = arg;
const fm_row = { ...fm };
const fm_row = { ...fm, render: local.render };
fm_row.data = props.row;
fm_row.render = fm.render;
local.tbl = tbl;
const key = props.column.key;
return (
<PassProp
idx={props.rowIdx}
row={props.row}
col={{
name: props.column.key,
name: key,
value: props.row[props.column.key],
depth: props.row.__depth || 0,
}}
rows={tbl.data}
fm={fm_row}
ext_fm={{
change: () => {},
remove: () => {
if (Array.isArray(fm.data[name])) {
fm.data[name] = value.filter(
(e: any) => e !== props.row
);
fm.render();
tbl.data = fm.data[name];
tbl.render();
}
fm.data[name] = tbl.data.filter(
(e: any) => e !== props.row
);
fm.render();
},
add: () => {
if (Array.isArray(value)) {
value.push({});
} else {
alert("value bukan array");
}
tbl.data.push({});
fm.render();
},
}}
>
@ -99,35 +145,23 @@ export const TableEdit: FC<{
</PassProp>
);
}}
render_row={(child, data) => {
return (
<div className="c-contents">
<BaseForm
is_form={false}
data={data}
on_submit={(form) => {}}
render={fm.render}
>
{(form) => {
return <>{child}</>;
}}
</BaseForm>
</div>
);
}}
/>
</div>
<PassProp
ext_fm={{
add: () => {
if (Array.isArray(value)) {
value.push({});
fm.data[name] = value;
fm.render();
} else {
fm.data[name] = [{}];
fm.render();
}
local.tbl.data.push({});
fm.render();
setTimeout(() => {
const last = Array.from(
ref.current?.querySelectorAll(".rdg-row") || []
).pop();
const input = last?.querySelector("input");
if (input) {
input.focus();
}
}, 100);
},
}}
>
@ -137,39 +171,3 @@ export const TableEdit: FC<{
</>
);
};
export const Footer = (
fm: FMLocal,
name: string,
PassProp: any,
child: any,
value: Array<any>
) => {
return (
<>
{/* <div>
<PassProp
ext_fm={{
remove: () => {
if (Array.isArray(fm.data[name])) {
fm.data[name] = value.filter(
(e: any) => e !== props.row
);
fm.render();
tbl.data = fm.data[name];
tbl.render();
console.log(fm.data)
}
},
add: () => {
if (Array.isArray(value)) {
value.push({});
} else {
alert("value bukan array");
}
},
}}></PassProp>
</div> */}
</>
);
};

View File

@ -1,8 +1,8 @@
import { useLocal } from "@/utils/use-local";
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { FieldLoading } from "lib/comps/ui/field-loading";
import { Typeahead } from "lib/comps/ui/typeahead";
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
export const TypeDropdown: FC<{
field: FieldLocal;
@ -11,7 +11,7 @@ export const TypeDropdown: FC<{
}> = ({ field, fm, arg }) => {
const local = useLocal({
loaded: false,
options: [],
options: [] as { value: string; label: string; data: any }[],
});
let value =
typeof arg.opt_get_value === "function"
@ -22,6 +22,7 @@ export const TypeDropdown: FC<{
type: field.type,
})
: fm.data[field.name];
useEffect(() => {
if (typeof arg.on_load === "function") {
const options = arg.on_load({});
@ -38,6 +39,22 @@ export const TypeDropdown: FC<{
} else {
local.options = res;
}
if (
field.type === "single-option" &&
!value &&
field.required &&
local.options.length > 0
) {
arg.opt_set_value({
fm,
name: field.name,
type: field.type,
options: local.options,
selected: [local.options[0]?.value],
});
}
local.loaded = true;
local.render();
});
@ -48,12 +65,16 @@ export const TypeDropdown: FC<{
}
}
}, []);
if (!local.loaded) return <FieldLoading />;
if (field.type === "single-option")
if (field.type === "single-option") {
if (value === null) {
fm.data[field.name] = undefined;
}
return (
<>
<Typeahead
value={value}
value={Array.isArray(value) ? value : [value]}
onSelect={({ search, item }) => {
if (item) {
arg.opt_set_value({
@ -77,6 +98,7 @@ export const TypeDropdown: FC<{
/>
</>
);
}
return (
<>

View File

@ -39,7 +39,8 @@ export const FieldTypeInput: FC<{
arg: FieldProp;
}> = ({ field, fm, prop, arg }) => {
const input = useLocal({
showHidePassword: false,
show_pass: false,
change_timeout: null as any,
});
let type_field = prop.sub_type;
switch (type_field) {
@ -49,7 +50,7 @@ export const FieldTypeInput: FC<{
default:
}
if (input.showHidePassword) {
if (input.show_pass) {
type_field = "text";
}
@ -80,13 +81,23 @@ export const FieldTypeInput: FC<{
value = Number(value) || null;
}
const renderOnChange = () => {
input.render();
if (field.on_change) {
field.on_change({ value: fm.data[field.name], name: field.name, fm });
}
clearTimeout(input.change_timeout);
input.change_timeout = setTimeout(fm.render, 300);
};
return (
<>
{type_field === "textarea" ? (
<AutoHeightTextarea
onChange={(ev) => {
fm.data[field.name] = ev.currentTarget.value;
fm.render();
renderOnChange();
}}
value={value || ""}
disabled={field.disabled}
@ -165,7 +176,7 @@ export const FieldTypeInput: FC<{
fm.data[field.name] = value?.startDate
? new Date(value?.startDate)
: null;
fm.render();
renderOnChange();
}}
/>
</>
@ -173,6 +184,7 @@ export const FieldTypeInput: FC<{
<div className="c-flex c-relative c-flex-1">
<input
type={type_field}
tabIndex={0}
onChange={(ev) => {
if (["date", "datetime", "datetime-local"].includes(type_field)) {
let result = null;
@ -183,7 +195,7 @@ export const FieldTypeInput: FC<{
} else {
fm.data[field.name] = ev.currentTarget.value;
}
fm.render();
renderOnChange();
}}
placeholder={arg.placeholder || ""}
value={value}
@ -195,6 +207,9 @@ export const FieldTypeInput: FC<{
display = "";
field.render();
}}
onKeyDown={(e) => {
if (e.key === "Enter" && fm.status === "ready") fm.submit();
}}
onBlur={() => {
field.focused = false;
field.render();
@ -204,12 +219,12 @@ export const FieldTypeInput: FC<{
<div
className="c-absolute c-right-0 c-h-full c-flex c-items-center c-cursor-pointer"
onClick={() => {
input.showHidePassword = !input.showHidePassword;
input.show_pass = !input.show_pass;
input.render();
}}
>
<div className="">
{input.showHidePassword ? (
{input.show_pass ? (
<EyeIcon className="c-h-4" />
) : (
<EyeOff className="c-h-4" />

View File

@ -16,7 +16,7 @@ export const FieldMoney: FC<{
ref: null as any,
});
let display: any = null;
const money = formatMoney(Number(value) || 0)
const money = formatMoney(Number(value) || 0);
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
<div
@ -42,6 +42,13 @@ export const FieldMoney: FC<{
onChange={(ev) => {
fm.data[field.name] = ev.currentTarget.value;
fm.render();
if (field.on_change) {
field.on_change({
value: fm.data[field.name],
name: field.name,
fm,
});
}
}}
value={value}
disabled={field.disabled}

View File

@ -15,8 +15,6 @@ export const FieldRichText: FC<{
});
useEffect(() => {
if (local.ref) {
console.log(local.ref);
console.log(local.ref.current);
const q = new Quill(local.ref, {
theme: "snow",
modules: {

View File

@ -14,27 +14,27 @@ export const FieldUpload: FC<{
value: 0 as any,
display: false as any,
ref: null as any,
drop: false as boolean
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");
onDragOver={(e: any) => {
// 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")}
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

View File

@ -1,16 +1,13 @@
import { generateSelect } from "lib/comps/md/gen/md-select";
import { on_load } from "lib/comps/md/gen/tbl-list/on_load";
import { createItem, parseGenField } from "lib/gen/utils";
import capitalize from "lodash.capitalize";
import { ArrowBigDown } from "lucide-react";
import { on_load_rel } from "./on_load_rel";
import { createId } from "@paralleldrive/cuid2";
import { gen_label } from "./gen-label";
import { get_value } from "./get-value";
import { set_value } from "./set-value";
import get from "lodash.get";
import { generateRelation } from "./gen-rel";
import { gen_rel_many } from "./gen-rel-many";
import { genTableEdit } from "./gen-table-edit";
import { get_value } from "./get-value";
import { on_load_rel } from "./on_load_rel";
import { set_value } from "./set-value";
import { createId } from "@paralleldrive/cuid2";
export type GFCol = {
name: string;
type: string;
@ -22,9 +19,9 @@ export type GFCol = {
fields: GFCol[];
};
};
export const newField = (
export const newField = async (
arg: GFCol,
opt: { parent_table: string; value: Array<string> },
opt: { parent_table: string; value: Array<string>; on_change?: string },
show_label: boolean
) => {
let show = typeof show_label === "boolean" ? show_label : true;
@ -43,6 +40,9 @@ export const newField = (
child: {
childs: [],
},
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
},
},
});
@ -59,6 +59,9 @@ export const newField = (
child: {
childs: [],
},
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
},
},
});
@ -73,6 +76,9 @@ export const newField = (
ext__show_label: show ? "y" : "n",
type: "single-option",
sub_type: "toogle",
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
},
},
});
@ -89,6 +95,9 @@ export const newField = (
child: {
childs: [],
},
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
},
},
});
@ -102,6 +111,12 @@ export const newField = (
pks: {},
});
if (["has-one"].includes(arg.type)) {
const rel__gen_fields = JSON.stringify(
arg.relation?.fields.map((e) => {
const v = (e as any).value;
return v;
})
);
return createItem({
component: {
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
@ -112,6 +127,7 @@ export const newField = (
ext__show_label: show ? "y" : "n",
sub_type: "dropdown",
rel__gen_table: arg.name,
rel__gen_fields: [rel__gen_fields, rel__gen_fields],
opt__on_load: [load],
opt__label: [
gen_label({
@ -137,6 +153,9 @@ export const newField = (
child: {
childs: [],
},
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
},
},
});
@ -146,48 +165,54 @@ export const newField = (
arg,
rel: fields,
});
if (result.on_load) {
return createItem({
component: {
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
props: {
name: arg.name,
label: formatName(arg.name),
type: "multi-option",
sub_type: "checkbox",
rel__gen_table: arg.name,
opt__on_load: [result.on_load],
ext__show_label: show ? "y" : "n",
opt__label: [result.get_label],
opt__get_value: [result.get_value],
opt__set_value: [result.set_value],
child: {
childs: [],
},
let child: any = { childs: [] };
let rel__gen_fields: any = undefined;
let sub_type = "checkbox";
if (arg.relation?.fields?.length > 1) {
sub_type = "table-edit";
rel__gen_fields = JSON.stringify(
arg.relation?.fields.map((e) => {
const v = (e as any).value;
return v;
})
);
child = createItem({
childs: await generateRelation(
{
rel__gen_fields: { value: rel__gen_fields },
rel__gen_table: { value: JSON.stringify(arg.name) },
sub_type: { value: "'table-edit'" },
},
},
});
} else {
return createItem({
component: {
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
props: {
name: arg.name,
ext__show_label: show ? "y" : "n",
label: formatName(arg.name),
type: "-",
ext__width: "full",
sub_type: "-",
msg_error: `\
Select type (multi-option) and sub type (table-edit) select table(${arg.name}) and fields Click generate
`,
child: {
childs: [],
},
},
},
createItem({}),
false
),
});
}
return createItem({
component: {
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
props: {
name: arg.name,
label: formatName(arg.name),
type: "multi-option",
sub_type,
rel__gen_table: arg.name,
opt__on_load: [result.on_load],
ext__show_label: show ? "y" : "n",
opt__label: [result.get_label],
opt__get_value: [result.get_value],
opt__set_value: [result.set_value],
rel__gen_fields: rel__gen_fields
? [rel__gen_fields, rel__gen_fields]
: undefined,
child,
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
},
},
});
}
} else {
// type not found,
@ -203,6 +228,9 @@ export const newField = (
child: {
childs: [],
},
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
},
},
});

View File

@ -11,9 +11,15 @@ export const generateForm = async (
modify: (data: any) => void,
data: any,
item: PrasiItem,
commit: boolean
commit: boolean,
is_md?: boolean
) => {
const table = data.gen__table.value as string;
let table = "" as string;
try {
table = eval(data.gen__table.value);
} catch (e) {
table = data.gen__table.value;
}
const raw_fields = JSON.parse(data.gen__fields.value) as (
| string
| { value: string; checked: string[] }
@ -34,280 +40,341 @@ export const generateForm = async (
if (data["on_load"]) {
result.on_load = {
mode: "raw",
value: on_load({ pk, table, select, pks }),
value: on_load({
pk,
table,
select,
pks,
opt: {
after_load: `
if (typeof md === "object") {
opt.fm.status = "ready";
md.render();
}
`,
},
}),
};
}
if (data["on_submit"]) {
result.on_submit = {
mode: "raw",
value: `\
async ({ form, error }: IForm) => {
let result = false;
try {
async ({ form, error, fm }: IForm) => {
let result = false;
try {
if (typeof md !== "undefined") {
fm.status = "saving";
md.render();
}
const data = { ...form }; // data form
const data_rel = ${JSON.stringify(
rel_many
)} // list relasi has many
const data_master = {} as Record<string, any> | any; // variabel untuk data master
const data_array = [] as Array<{
table: string;
data: Array<any>;
fk: string;
}>; // variabel untuk data array atau has many
const data = { ...form }; // data form
const data_rel = ${JSON.stringify(rel_many)} // list relasi has many
const data_master = {} as Record<string, any> | any; // variabel untuk data master
const data_array = [] as Array<{
table: string;
data: Array<any>;
fk: string;
}>; // variabel untuk data array atau has many
// proses untuk membagi antara data master dengan data array
// data array / has many dilihat dari value yang berupa array
for (const [k, v] of Object.entries(data) as any) {
if (Array.isArray(v)) {
const rel = Array.isArray(data_rel) && data_rel.length ? data_rel.find((e) => e.table === k) : null
if (rel) {
data_array.push({
table: k,
data: v,
fk: rel.fk,
});
}
} else {
data_master[k] = v;
}
}
// hapus id dari data_master jika ada
try {
delete data_master.${pk};
} catch (ex) {}
if (form.${pk}) {
await db.${table}.update({
where: {
// proses untuk membagi antara data master dengan data array
// data array / has many dilihat dari value yang berupa array
for (const [k, v] of Object.entries(data) as any) {
if (Array.isArray(v)) {
const rel = Array.isArray(data_rel) && data_rel.length ? data_rel.find((e) => e.table === k) : null
if (rel) {
data_array.push({
table: k,
data: v,
fk: rel.fk,
});
}
} else {
data_master[k] = v;
}
}
// hapus id dari data_master jika ada
try {
delete data_master.${pk};
} catch (ex) {}
if (form.${pk}) {
await db.${table}.update({
where: {
${pk}: form.${pk},
},
data: data_master,
});
} else {
const res = await db.${table}.create({
data: data_master,
});
if (res) form.${pk} = res.${pk};
}
if (data_array.length) {
const exec_query_bulk = async (
current: { table: string; data: Array<any>; fk: string },
list: Array<{ table: string; data: Array<any>; fk: string }>,
index: number,
) => {
if (list.length) {
const data = current.data.map((e) => {
return {
...e,
${table}: {
connect: {
${pk}: form.${pk},
},
data: data_master,
});
} else {
const res = await db.${table}.create({
data: data_master,
});
if (res) form.${pk} = res.${pk};
}
if (data_array.length) {
const exec_query_bulk = async (
current: { table: string; data: Array<any>; fk: string },
list: Array<{ table: string; data: Array<any>; fk: string }>,
index: number,
) => {
if (list.length) {
const data = current.data.map((e) => {
return {
...e,
${table}: {
connect: {
${pk}: form.${pk},
},
},
};
});
await db._batch.upsert({
table: current.table,
where: {
[current.fk]: form.${pk},
},
data: data,
mode: "relation",
} as any);
},
};
});
await db._batch.upsert({
table: current.table,
where: {
[current.fk]: form.${pk},
},
data: data,
mode: "relation",
} as any);
if (list.length > 1) {
try {
index++;
if (index < list.length - 1) {
await exec_query_bulk(list[index], list, index);
}
} catch (ex) {}
}
}
};
await exec_query_bulk(data_array[0], data_array, 0);
}
result = true;
} catch (e) {
console.error(e);
result = false;
if (list.length > 1) {
try {
index++;
if (index < list.length - 1) {
await exec_query_bulk(list[index], list, index);
}
} catch (ex) {}
}
}
};
await exec_query_bulk(data_array[0], data_array, 0);
}
result = true;
} catch (e) {
console.error(e);
result = false;
}
return result;
if (typeof md !== "undefined") {
fm.status = "ready";
md.render();
}
return result;
};
type IForm = { form: any; error: Record<string, string>; fm: FMLocal }
`,
};
}
if (typeof is_md === "boolean" && is_md) {
result.on_init = {
mode: "raw",
value: `\
({ submit, reload, fm }: Init) => {
// on init
if (!isEditor) {
if (typeof md === "object") {
md.childs["form"] = {
fm: fm
};
}
}
};
type IForm = { form: any; error: Record<string, string> }
type Init = { submit: () => Promise<boolean>; reload: () => void; fm: FMLocal }
`,
};
}
const childs = [];
console.log({fields})
for (const item of fields.filter((e) => !e.is_pk)) {
let value = [] as Array<string>;
if (["has-one", "has-many"].includes(item.type)) {
value = get(item, "value.checked") as any;
}
const field = newField(item, { parent_table: table, value }, true);
const field = await newField(item, { parent_table: table, value }, true);
childs.push(field);
}
childs.push({
id: createId(),
dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" },
name: "submit",
type: "item",
childs: [
{
id: createId(),
dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" },
name: "bottom",
type: "item",
childs: [
{
id: createId(),
adv: {
js: '<Button\n {...props}\n onClick={(e) => {\n console.log(isEditor);\n if (!isEditor) on_click(e);\n }}\n variant={variant !== "primary" ? variant : undefined}\n size={size !== "default" ? size : undefined}\n>\n {label}\n</Button>',
css: "& {\n display: flex;\n box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;\n\n &:hover {\n cursor: pointer;\n\n\n\n\n\n // &.mobile {}\n // &.desktop {}\n // &:hover {}\n }\n}",
jsBuilt:
'render(/* @__PURE__ */ React.createElement(\n Button,\n {\n ...props,\n onClick: (e) => {\n console.log(isEditor);\n if (!isEditor)\n on_click(e);\n },\n variant: variant !== "primary" ? variant : void 0,\n size: size !== "default" ? size : void 0\n },\n label\n));\n',
},
dim: { h: "full", w: "full" },
name: "button",
type: "item",
childs: [],
mobile: { linktag: {} },
script: {
props: {
size: { value: ' "default";\n' },
variant: { value: ' "primary";\n' },
on_click: {
value:
" (e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n};\n",
if (typeof is_md === "boolean" && !is_md)
childs.push({
id: createId(),
dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" },
name: "submit",
type: "item",
childs: [
{
id: createId(),
dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" },
name: "bottom",
type: "item",
childs: [
{
id: createId(),
adv: {
js: '<Button\n {...props}\n onClick={(e) => {\n console.log(isEditor);\n if (!isEditor) on_click(e);\n }}\n variant={variant !== "primary" ? variant : undefined}\n size={size !== "default" ? size : undefined}\n>\n {label}\n</Button>',
css: "& {\n display: flex;\n box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;\n\n &:hover {\n cursor: pointer;\n\n\n\n\n\n // &.mobile {}\n // &.desktop {}\n // &:hover {}\n }\n}",
jsBuilt:
'render(/* @__PURE__ */ React.createElement(\n Button,\n {\n ...props,\n onClick: (e) => {\n console.log(isEditor);\n if (!isEditor)\n on_click(e);\n },\n variant: variant !== "primary" ? variant : void 0,\n size: size !== "default" ? size : void 0\n },\n label\n));\n',
},
dim: { h: "full", w: "full" },
name: "button",
type: "item",
childs: [],
mobile: { linktag: {} },
script: {
props: {
size: { value: ' "default";\n' },
variant: { value: ' "primary";\n' },
on_click: {
value:
" (e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n};\n",
},
},
},
},
component: {
id: "a15d152d-0118-408f-89f1-f6b2dfbd2e05",
props: {
size: {
idx: 5,
meta: {
type: "option",
options: '["default", "sm", "lg", "icon","nosize"]',
optionsBuilt:
' ["default", "sm", "lg", "icon", "nosize"];\n',
},
name: "prop_5",
type: "string",
value: '"default"',
valueBuilt: ' "default";\n',
},
label: {
idx: 1,
meta: { type: "content-element" },
name: "prop_1",
type: "string",
value: '"hello"',
content: {
id: createId(),
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n {children}\n</div>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, children));\n',
component: {
id: "a15d152d-0118-408f-89f1-f6b2dfbd2e05",
props: {
size: {
idx: 5,
meta: {
type: "option",
options: '["default", "sm", "lg", "icon","nosize"]',
optionsBuilt:
' ["default", "sm", "lg", "icon", "nosize"];\n',
},
dim: { h: "full", w: "full" },
name: "label",
type: "item",
childs: [
{
id: createId(),
name: "Wrapped",
type: "item",
childs: [
{
id: createId(),
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2c0-1.886 0-2.828-.586-3.414C14.828 15 13.886 15 12 15h-1c-1.886 0-2.828 0-3.414.586C7 16.172 7 17.114 7 19v2" /><path stroke-linecap="round" d="M7 8h5" /><path d="M3 9c0-2.828 0-4.243.879-5.121C4.757 3 6.172 3 9 3h7.172c.408 0 .613 0 .796.076c.184.076.329.22.618.51l2.828 2.828c.29.29.434.434.51.618c.076.183.076.388.076.796V15c0 2.828 0 4.243-.879 5.121C19.243 21 17.828 21 15 21H9c-2.828 0-4.243 0-5.121-.879C3 19.243 3 17.828 3 15z" /></g></svg>\n</div>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24" }, /* @__PURE__ */ React.createElement("g", { fill: "none", stroke: "currentColor", "stroke-width": "2" }, /* @__PURE__ */ React.createElement("path", { d: "M16 21v-2c0-1.886 0-2.828-.586-3.414C14.828 15 13.886 15 12 15h-1c-1.886 0-2.828 0-3.414.586C7 16.172 7 17.114 7 19v2" }), /* @__PURE__ */ React.createElement("path", { "stroke-linecap": "round", d: "M7 8h5" }), /* @__PURE__ */ React.createElement("path", { d: "M3 9c0-2.828 0-4.243.879-5.121C4.757 3 6.172 3 9 3h7.172c.408 0 .613 0 .796.076c.184.076.329.22.618.51l2.828 2.828c.29.29.434.434.51.618c.076.183.076.388.076.796V15c0 2.828 0 4.243-.879 5.121C19.243 21 17.828 21 15 21H9c-2.828 0-4.243 0-5.121-.879C3 19.243 3 17.828 3 15z" })))));\n',
},
dim: { h: "full", w: "full" },
html: "aadd",
name: "new_text",
text: "",
type: "text",
layout: { dir: "col", gap: 0, align: "center" },
script: {},
},
{
id: createId(),
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n Save\n</div>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, "Save"));\n',
},
dim: { h: "full", w: "full" },
name: "new_item",
type: "item",
childs: [],
script: {},
},
],
layout: {
dir: "row",
gap: 10,
wrap: "flex-nowrap",
align: "top-left",
},
name: "prop_5",
type: "string",
value: '"default"',
valueBuilt: ' "default";\n',
},
label: {
idx: 1,
meta: { type: "content-element" },
name: "prop_1",
type: "string",
value: '"hello"',
content: {
id: createId(),
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n {children}\n</div>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, children));\n',
},
],
hidden: false,
layout: {
dir: "col",
gap: 0,
wrap: "flex-nowrap",
align: "center",
dim: { h: "full", w: "full" },
name: "label",
type: "item",
childs: [
{
id: createId(),
name: "Wrapped",
type: "item",
childs: [
{
id: createId(),
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2c0-1.886 0-2.828-.586-3.414C14.828 15 13.886 15 12 15h-1c-1.886 0-2.828 0-3.414.586C7 16.172 7 17.114 7 19v2" /><path stroke-linecap="round" d="M7 8h5" /><path d="M3 9c0-2.828 0-4.243.879-5.121C4.757 3 6.172 3 9 3h7.172c.408 0 .613 0 .796.076c.184.076.329.22.618.51l2.828 2.828c.29.29.434.434.51.618c.076.183.076.388.076.796V15c0 2.828 0 4.243-.879 5.121C19.243 21 17.828 21 15 21H9c-2.828 0-4.243 0-5.121-.879C3 19.243 3 17.828 3 15z" /></g></svg>\n</div>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24" }, /* @__PURE__ */ React.createElement("g", { fill: "none", stroke: "currentColor", "stroke-width": "2" }, /* @__PURE__ */ React.createElement("path", { d: "M16 21v-2c0-1.886 0-2.828-.586-3.414C14.828 15 13.886 15 12 15h-1c-1.886 0-2.828 0-3.414.586C7 16.172 7 17.114 7 19v2" }), /* @__PURE__ */ React.createElement("path", { "stroke-linecap": "round", d: "M7 8h5" }), /* @__PURE__ */ React.createElement("path", { d: "M3 9c0-2.828 0-4.243.879-5.121C4.757 3 6.172 3 9 3h7.172c.408 0 .613 0 .796.076c.184.076.329.22.618.51l2.828 2.828c.29.29.434.434.51.618c.076.183.076.388.076.796V15c0 2.828 0 4.243-.879 5.121C19.243 21 17.828 21 15 21H9c-2.828 0-4.243 0-5.121-.879C3 19.243 3 17.828 3 15z" })))));\n',
},
dim: { h: "full", w: "full" },
html: "aadd",
name: "new_text",
text: "",
type: "text",
layout: { dir: "col", gap: 0, align: "center" },
script: {},
},
{
id: createId(),
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n Save\n</div>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, "Save"));\n',
},
dim: { h: "full", w: "full" },
name: "new_item",
type: "item",
childs: [],
script: {},
},
],
layout: {
dir: "row",
gap: 10,
wrap: "flex-nowrap",
align: "top-left",
},
},
],
hidden: false,
layout: {
dir: "col",
gap: 0,
wrap: "flex-nowrap",
align: "center",
},
script: {},
},
script: {},
valueBuilt: '"hello"',
},
valueBuilt: '"hello"',
},
variant: {
idx: 3,
meta: {
type: "option",
options:
'["primary", "secondary", "outline", "ghost", "link", "destructive"]',
option_mode: "button",
optionsBuilt:
' ["primary", "secondary", "outline", "ghost", "link", "destructive"];\n',
variant: {
idx: 3,
meta: {
type: "option",
options:
'["primary", "secondary", "outline", "ghost", "link", "destructive"]',
option_mode: "button",
optionsBuilt:
' ["primary", "secondary", "outline", "ghost", "link", "destructive"];\n',
},
name: "prop_3",
type: "string",
value: '"primary"',
valueBuilt: ' "primary";\n',
},
on_click: {
idx: 1,
meta: { type: "text" },
name: "prop_1",
type: "string",
value:
"(e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n}",
valueBuilt:
" (e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n};\n",
},
name: "prop_3",
type: "string",
value: '"primary"',
valueBuilt: ' "primary";\n',
},
on_click: {
idx: 1,
meta: { type: "text" },
name: "prop_1",
type: "string",
value:
"(e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n}",
valueBuilt:
" (e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n};\n",
},
},
},
],
layout: {
dir: "col",
gap: 0,
wrap: "flex-nowrap",
align: "center",
},
],
layout: { dir: "col", gap: 0, wrap: "flex-nowrap", align: "center" },
},
],
padding: { b: 10, l: 10, r: 10, t: 10 },
});
},
],
padding: { b: 10, l: 10, r: 10, t: 10 },
});
const body_prop = {
adv: {
js: "<div\n {...props}\n className={cx(\n props.className,\n css`\n align-content: start;`,\n )}\n>\n {children}\n</div>",
jsBuilt:
'render(/* @__PURE__ */ React.createElement(\n "div",\n {\n ...props,\n className: cx(\n props.className,\n css`\n align-content: start;`\n )\n },\n children\n));\n',
},
dim: {
h: "full",
w: "full",
},
layout: {
dir: "row",
gap: 0,
wrap: "flex-wrap",
align: "top-left",
},
};
if (commit) {
Object.keys(result).map((e) => {
item.edit.setProp(e, result[e]);
@ -316,12 +383,13 @@ export const generateForm = async (
mode: "jsx",
value: createItem({
name: "item",
...body_prop,
childs: childs,
}),
});
await item.edit.commit();
} else {
console.log({ data });
set(data, "body.value", { ...data.body?.value, ...body_prop });
set(data, "body.value.childs", childs);
Object.keys(result).map((e) => {
set(data, e, result[e]);

View File

@ -16,20 +16,29 @@ export const gen_label = ({
}
return `\
(row: { value: string; label: string; item?: any }) => {
(row: { value: string; label: string; data?: any }) => {
const cols = ${JSON.stringify(cols)};
if (isEditor) {
return row.label;
}
const result = [];
if (!!row.item && !Array.isArray(row.item)) {
cols.map((e) => {
if (row.item[e]) {
result.push(row.item[e]);
}
});
return result.join(" - ");
if (!!row.data && !row.label && !Array.isArray(row.data)) {
if(cols.length > 0){
cols.map((e) => {
if (row.data[e]) {
result.push(row.data[e]);
}
});
return result.join(" - ");
} else {
const fields = parseGenField(rel__gen_fields);
return fields
.filter((e) => !e.is_pk)
.map((e) => row.data[e.name])
.filter((e) => e)
.join(" - ");
}
}
return row.label;
}

View File

@ -8,7 +8,6 @@ export const gen_rel_many = (prop: {
rel: any;
}) => {
const { table_parent, arg, rel } = prop;
console.log({ rel });
const parent = rel.find((e: any) => e.name === table_parent);
const master = rel.find(
(e: any) => e.name !== table_parent && e.type === "has-one"
@ -124,20 +123,29 @@ export const gen_rel_many = (prop: {
}
}
const get_label = `\
(row: { value: string; label: string; item?: any }) => {
(row: { value: string; label: string; data?: any }) => {
const cols = ${JSON.stringify(cols)};
if (isEditor) {
return row.label;
}
const result = [];
if (!!row.item && !Array.isArray(row.item)) {
cols.map((e) => {
if (row.item[e]) {
result.push(row.item[e]);
}
});
return result.join(" - ");
if (!!row.data && !row.label && !Array.isArray(row.data)) {
if(cols.length > 0){
cols.map((e) => {
if (row.data[e]) {
result.push(row.data[e]);
}
});
return result.join(" - ");
} else {
const fields = parseGenField(rel__gen_fields);
return fields
.filter((e) => !e.is_pk)
.map((e) => row.data[e.name])
.filter((e) => e)
.join(" - ");
}
}
return row.label;
}
@ -147,13 +155,13 @@ export const gen_rel_many = (prop: {
result.get_value = get_value;
result.set_value = set_value;
} else {
console.log("tidak punya master");
result.get_label = `\
(row: { value: string; label: string; item?: any }) => {
return row.label;
}
`;
result.get_value = `\
(arg: {
options: { label: string; value: string; item?: string }[];

View File

@ -21,17 +21,24 @@ export const generateRelation = async (
},
false
)) as any;
item.edit.setProp("child", {
mode: "jsx",
value: {
id: createId(),
name: "item",
type: "item",
edit: null as any,
childs: result,
},
});
await item.edit.commit();
if (commit) {
item.edit.setProp("child", {
mode: "jsx",
value: {
id: createId(),
name: "item",
type: "item",
edit: null as any,
childs: result,
},
});
await item.edit.commit();
return;
} else {
return result;
}
} else {
console.log(item.edit.props);
}
};

View File

@ -1,9 +1,8 @@
import { createId } from "@paralleldrive/cuid2";
import { generateSelect } from "lib/comps/md/gen/md-select";
import { createItem, parseGenField } from "lib/gen/utils";
import get from "lodash.get";
import { formatName, newField } from "./fields";
import { set } from "lib/utils/set";
import { createId } from "@paralleldrive/cuid2";
export const genTableEdit = async (
item: PrasiItem,
@ -33,41 +32,51 @@ export const genTableEdit = async (
}
const childs = [] as Array<any>;
let first = true;
fields
.map((e, idx) => {
if (e.is_pk) return;
let value = [] as Array<string>;
if (["has-one", "has-many"].includes(e.type)) {
value = get(e, "value.checked") as any;
}
const field = newField(e, { parent_table: table, value }, false);
let tree_depth = "";
let tree_depth_built = "";
if (first) {
tree_depth = `tree_depth={col.depth}`;
tree_depth_built = `tree_depth:col.depth`;
first = false;
}
childs.push({
component: {
id: "297023a4-d552-464a-971d-f40dcd940b77",
props: {
name: e.name,
title: formatName(e.name),
child: createItem({
childs: [field],
}),
await Promise.all(
fields
.map(async (e, idx) => {
if (e.is_pk) return;
let value = [] as Array<string>;
if (["has-one", "has-many"].includes(e.type)) {
value = get(e, "value.checked") as any;
}
const field = await newField(
e,
{
parent_table: table,
value,
on_change: `() => { ext_fm.change(); }`,
},
},
});
})
.filter((e) => e) as any;
false
);
let tree_depth = "";
let tree_depth_built = "";
if (first) {
tree_depth = `tree_depth={col.depth}`;
tree_depth_built = `tree_depth:col.depth`;
first = false;
}
childs.push({
component: {
id: "297023a4-d552-464a-971d-f40dcd940b77",
props: {
name: e.name,
title: formatName(e.name),
child: createItem({
childs: [field],
}),
},
},
});
})
.filter((e) => e)
);
childs.push({
component: {
id: "297023a4-d552-464a-971d-f40dcd940b77",
props: {
name: "option",
title: "option",
title: "",
child: {
id: createId(),
name: "option",
@ -133,7 +142,11 @@ export const genTableEdit = async (
name: "new_text",
text: "",
type: "text",
layout: { dir: "col", gap: 0, align: "center" },
layout: {
dir: "col",
gap: 0,
align: "left",
},
script: {},
},
],
@ -240,7 +253,7 @@ export const genTableEdit = async (
name: "new_text",
text: "",
type: "text",
layout: { dir: "col", gap: 0, align: "center" },
layout: { dir: "col", gap: 0, align: "left" },
script: {},
},
],
@ -285,7 +298,7 @@ export const genTableEdit = async (
dir: "row",
gap: 10,
wrap: "flex-nowrap",
align: "top-left",
align: "left",
},
padding: { b: 0, l: 10, r: 10, t: 0 },
},
@ -304,6 +317,7 @@ export const genTableEdit = async (
name: "btn-submit",
type: "item",
edit: null as any,
padding: { b: 10, l: 10, r: 10, t: 0 },
childs: [
{
id: createId(),
@ -354,12 +368,17 @@ export const genTableEdit = async (
const option = {
id: createId(),
name: "bottom",
padding: { b: 10, l: 10, r: 10, t: 0 },
type: "item",
childs: [
{
id: createId(),
name: "bottom",
name: "wrapper",
type: "item",
dim: {
h: "fit",
w: "fit",
},
childs: [
{
id: createId(),
@ -380,7 +399,7 @@ export const genTableEdit = async (
variant: { value: ' "primary";\n' },
on_click: {
value:
' (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (typeof ext_fm === "object") {\n console.log("masuk");\n ext_fm.add();\n }\n};\n',
' (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (typeof ext_fm === "object") {\n ext_fm.add();\n }\n};\n',
},
},
},
@ -436,7 +455,7 @@ export const genTableEdit = async (
name: "new_text",
text: "",
type: "text",
layout: { dir: "col", gap: 0, align: "center" },
layout: { dir: "col", gap: 0, align: "left" },
script: {},
},
{
@ -456,9 +475,9 @@ export const genTableEdit = async (
],
layout: {
dir: "row",
gap: 10,
gap: 5,
wrap: "flex-nowrap",
align: "top-left",
align: "left",
},
},
],
@ -488,9 +507,9 @@ export const genTableEdit = async (
name: "prop_1",
type: "string",
value:
'(e) => {\n e.preventDefault();\n e.stopPropagation();\n if (typeof ext_fm === "object") {\n console.log("masuk")\n ext_fm.add();\n // const value = fm.data[name] || [];\n // if (Array.isArray(value)) {\n // value.push({});\n // fm.render();\n // } else {\n // value = [];\n // fm.render();\n // }\n }\n}',
'(e) => {\n e.preventDefault();\n e.stopPropagation();\n if (typeof ext_fm === "object") {\n ext_fm.add();\n }\n}',
valueBuilt:
' (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (typeof ext_fm === "object") {\n console.log("masuk");\n ext_fm.add();\n }\n};\n',
' (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (typeof ext_fm === "object") {\n ext_fm.add();\n }\n};\n',
},
},
ref_ids: {},

View File

@ -1,7 +1,7 @@
export const get_value = ({
pk,
table,
select
select,
}: {
pk: string;
table: string;
@ -15,30 +15,32 @@ export const get_value = ({
}
}
return `\
(arg: {
options: { label: string; value: string; item?: string }[];
fm: FMLocal;
name: string;
type: string;
}) => {
const { options, fm, name, type } = arg;
if(isEditor){
return fm.data[name];
(arg: {
options: { label: string; value: string; item?: string }[];
fm: FMLocal;
name: string;
type: string;
}) => {
const { options, fm, name, type } = arg;
if(isEditor){
return fm.data[name];
}
let result = null;
result = fm.data[name];
try{
const data = fm.data["${table}"];
if(typeof data === "object"){
if(typeof data?.connect?.${pk} === "string"){
result = data.connect.${pk};
} else if (typeof data?.id === "string") {
result = data.id;
} else if (data?.disconnect === true) {
result = undefined;
}
let result = null;
result = fm.data[name];
try{
const data = fm.data["${table}"];
if(typeof data === "object"){
if(typeof data?.connect?.${pk} === "string"){
result = data.connect.${pk};
}else if (typeof data?.id === "string") {
result = data.id;
}
}
}catch(ex){
}
return result;
}
}catch(ex){
}
return result;
}
`;
};

View File

@ -12,8 +12,8 @@ export const on_load = ({
select: any;
pks: Record<string, string>;
opt?: {
before_load: string;
after_load: string;
before_load?: string;
after_load?: string;
};
}) => {
const sample: any = {};
@ -41,19 +41,19 @@ async (opt) => {
}
}
${
opt?.before_load
? opt.before_load
: `let id = raw_id`
}
${opt?.before_load ? opt.before_load : `let id = raw_id`}
let item = {};
let where = {
${pk}: id,
};
if (id){
item = await db.${table}.findFirst({
where: {
${pk}: id,
},
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
const table = db[gen__table] as any;
const fields = parseGenField(gen__fields);
const gen = generateSelect(fields);
item = await table?.findFirst({
where,
select: gen.select,
});
${opt?.after_load ? opt?.after_load : ""}

View File

@ -1,27 +1,27 @@
export const on_load_rel = ({
pk,
table,
select,
pks,
}: {
pk: string;
table: string;
select: any;
pks: Record<string, string>;
}) => {
const sample = {
label: "sample",
value: "sample",
data: null
} as any;
const cols = [];
for (const [k, v] of Object.entries(select) as any) {
if(k !== pk && typeof v !== "object"){
cols.push(k);
}
pk,
table,
select,
pks,
}: {
pk: string;
table: string;
select: any;
pks: Record<string, string>;
}) => {
const sample = {
label: "sample",
value: "sample",
data: null,
} as any;
const cols = [];
for (const [k, v] of Object.entries(select) as any) {
if (k !== pk && typeof v !== "object") {
cols.push(k);
}
}
return `\
return `\
(arg: {
reload: () => Promise<void>;
orderBy?: Record<string, "asc" | "desc">;
@ -35,8 +35,14 @@ export const on_load_rel = ({
return await db.${table}.count();
}
const fields = parseGenField(rel__gen_fields);
const res = generateSelect(fields);
const items = await db.${table}.findMany({
select: ${JSON.stringify(select)},
select: {
...${JSON.stringify(select)},
...(res?.select || {})
},
orderBy: arg.orderBy || {
${pk}: "desc"
},
@ -53,18 +59,27 @@ export const on_load_rel = ({
})
return result.join(" - ");
}
done(items.map((e) => {
return {
let blank: any = undefined;
if (ext__required !== "y") {
blank = { value: undefined, label: "", data: {} };
}
done(
[
blank,
...items.map((e) => {
return {
value: e.${pk},
label: getLabel(e),
data: e,
}
}))
};
}),
].filter((e) => e),
);
} else {
done([])
}
})
}
`;
};
};

View File

@ -24,11 +24,17 @@ export const set_value = ({
}) => {
const { selected, options, fm, name, type } = arg;
if (type === "single-option") {
fm.data[name] = {
connect: {
${pk}: selected[0],
},
};
if (selected[0]) {
fm.data[name] = {
connect: {
${pk}: selected[0],
},
};
} else {
fm.data[name] = {
disconnect: true,
};
}
} else {
fm.data[name] = selected.map((e) => e);
}

View File

@ -18,6 +18,8 @@ export type FMProps = {
gen_fields: any;
gen_table: string;
on_load_deps?: any[];
feature?: any[];
sfd_field?: any;
};
export type GenField =
@ -87,7 +89,8 @@ export type FieldProp = {
placeholder: string;
show_label: boolean;
msg_error: string;
gen_table: string;
gen_table?: string;
gen_fields?: string;
};
export type FMInternal = {
@ -125,6 +128,9 @@ export type FMInternal = {
height: number;
field: "full" | "half";
};
soft_delete: {
field: any;
};
};
export type FMLocal = FMInternal & { render: () => void };
@ -150,6 +156,7 @@ export type FieldInternal<T extends FieldProp["type"]> = {
options: {
on_load?: () => Promise<{ value: string; label: string }[]>;
};
on_change?: (arg: { value: any, name: string, fm: FMLocal }) => void | Promise<void>;
prop?: any;
};
export type FieldLocal = FieldInternal<any> & {

View File

@ -13,7 +13,6 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
}
const { on_load, sonar } = fm.props;
fm.error = formError(fm);
fm.field_def = {};
const defs = parseGenField(fm.props.gen_fields);
for (const d of defs) {
@ -110,7 +109,7 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
};
if (typeof fm.props.on_submit === "function") {
if (fm.props.sonar === "on") {
if (fm.props.sonar === "on" && !isEditor) {
toast.loading(
<>
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
@ -126,7 +125,7 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
toast.dismiss();
done_all(success);
if (fm.props.sonar === "on") {
if (fm.props.sonar === "on" && !isEditor) {
setTimeout(() => {
toast.dismiss();

View File

@ -33,6 +33,7 @@ export const useField = (
required: required === "y",
required_msg: arg.required_msg,
disabled: arg.disabled === "y",
on_change: arg.on_change,
};
if (field.status === "init" || isEditor) {

View File

@ -4,19 +4,27 @@ import { fields_map } from "@/utils/format-value";
import { useLocal } from "@/utils/use-local";
import get from "lodash.get";
import { Loader2 } from "lucide-react";
import { ChangeEvent, FC, MouseEvent, ReactNode, useEffect } from "react";
import {
ChangeEvent,
FC,
MouseEvent,
ReactElement,
ReactNode,
useEffect,
} from "react";
import DataGrid, {
ColumnOrColumnGroup,
RenderCellProps,
Row,
SELECT_COLUMN_KEY,
SortColumn,
} from "react-data-grid";
import "./TableList.css";
import { createPortal } from "react-dom";
import { Toaster, toast } from "sonner";
import { Skeleton } from "../ui/skeleton";
import { sortTree } from "./utils/sort-tree";
import { filterWhere } from "../filter/utils/filter-where";
import { Skeleton } from "../ui/skeleton";
import "./TableList.css";
import { sortTree } from "./utils/sort-tree";
type OnRowClick = (arg: {
row: any;
@ -38,7 +46,6 @@ type TableListProp = {
}) => Promise<any[]>;
on_init: (arg?: any) => any;
mode: "table" | "list" | "grid" | "auto";
// _meta: Record<string, any>;
_item: PrasiItem;
gen_fields: string[];
row_click: OnRowClick;
@ -47,10 +54,16 @@ type TableListProp = {
feature?: Array<any>;
filter_name: string;
render_row?: (child: any, data: any) => ReactNode;
rowHeight?: number;
render_col?: (props: any) => ReactNode;
soft_delete_field: string;
row_height?: number;
render_col?: (arg: {
props: RenderCellProps<any, unknown>;
tbl: any;
child: any;
}) => ReactNode;
softdel_field?: string;
gen_table?: string;
softdel_type?: string;
cache_row?: boolean;
};
const w = window as any;
const selectCellClassname = css`
@ -77,11 +90,12 @@ export const TableList: FC<TableListProp> = ({
feature,
filter_name,
render_row,
rowHeight,
row_height: rowHeight,
render_col,
value
value,
cache_row,
}) => {
const where = get(w, `prasi_filter.${filter_name}`) ?? "hello";
const where = get(w, `prasi_filter.${filter_name}`) ? {} : {};
const whereQuery = filterWhere("hello");
if (mode === "auto") {
if (w.isMobile) {
@ -124,13 +138,13 @@ export const TableList: FC<TableListProp> = ({
}
},
},
cached_row: new WeakMap<any, ReactElement>(),
sort: {
columns: [] as SortColumn[],
on_change: (cols: SortColumn[]) => {
if (feature?.find((e) => e === "sorting")) {
local.sort.columns = cols;
local.paging.skip = 0;
if (cols.length > 0) {
const { columnKey, direction } = cols[0];
@ -179,14 +193,18 @@ export const TableList: FC<TableListProp> = ({
"asc" | "desc" | Record<string, "asc" | "desc">
>,
},
soft_delete: {
field: null as any,
},
});
if(typeof value !== "undefined"){
local.data = value;
}
// code ini digunakan untuk mengambil nama dari pk yang akan digunakan sebagai key untuk id
const pk = local.pk?.name || "id";
useEffect(() => {
if (isEditor) return;
if (isEditor || value) {
on_init(local);
return;
}
(async () => {
on_init(local);
if (local.status === "reload" && typeof on_load === "function") {
@ -198,7 +216,10 @@ export const TableList: FC<TableListProp> = ({
async reload() {},
where,
orderBy,
paging: { take: local.paging.take, skip: local.paging.skip },
paging: {
take: local.paging.take > 0 ? local.paging.take : undefined,
skip: local.paging.skip,
},
};
if (id_parent) {
load_args.paging = {};
@ -218,6 +239,7 @@ export const TableList: FC<TableListProp> = ({
}
})();
}, [local.status, on_load, local.sort.orderBy]);
const raw_childs = get(
child,
"props.meta.item.component.props.child.content.childs"
@ -341,7 +363,7 @@ export const TableList: FC<TableListProp> = ({
}
for (const child of childs) {
let key = getProp(child, "name", {});
const name = getProp(child, "title", {});
const name = getProp(child, "title", "");
const width = parseInt(getProp(child, "width", {}));
columns.push({
@ -352,7 +374,12 @@ export const TableList: FC<TableListProp> = ({
sortable: true,
renderCell(props) {
if (typeof render_col === "function")
return render_col({ props, tbl: local, child });
return render_col({
props,
tbl: local,
child,
});
return (
<PassProp
idx={props.rowIdx}
@ -389,9 +416,6 @@ export const TableList: FC<TableListProp> = ({
</>,
{
dismissible: true,
className: css`
background: #e4f7ff;
`,
}
);
} else {
@ -414,15 +438,20 @@ export const TableList: FC<TableListProp> = ({
}
}
}
if(typeof value === "undefined")
if (typeof value !== "undefined") {
local.data = value;
local.status = "ready" as any;
} else {
if (isEditor && local.status !== "ready") {
if (local.data.length === 0) {
const load_args: any = {
async reload() {},
where,
paging: { take: local.paging.take, skip: local.paging.skip },
paging: {
take: local.paging.take > 0 ? local.paging.take : undefined,
skip: local.paging.skip,
},
};
if (id_parent) load_args.paging = {};
if (typeof on_load === "function") {
local.data = on_load({ ...load_args, mode: "query" }) as any;
@ -430,9 +459,7 @@ export const TableList: FC<TableListProp> = ({
}
local.status = "ready";
}
let selected_idx = -1;
}
let data = local.data || [];
if (id_parent && local.pk && local.sort.columns.length === 0) {
@ -498,12 +525,20 @@ export const TableList: FC<TableListProp> = ({
columns={columns}
rows={data}
onScroll={local.paging.scroll}
selectedRows={new Set() as ReadonlySet<any>}
onSelectedCellChange={() => {}}
onSelectedRowsChange={() => {}}
renderers={
local.status !== "ready"
? undefined
: {
renderRow(key, props) {
const is_selected = selected_idx === props.rowIdx;
if (
cache_row === true &&
local.cached_row.has(props.row)
) {
return local.cached_row.get(props.row);
}
const isSelect = selected({
idx: props.rowIdx,
row: props.row,
@ -533,16 +568,10 @@ export const TableList: FC<TableListProp> = ({
)}
/>
);
if (typeof render_row === "function") {
return render_row(child_row, props.row);
if (cache_row) {
local.cached_row.set(props.row, child_row);
}
// return child_row;
return (
<>
<div className="c-contents">{child_row}</div>
</>
);
return child_row;
},
noRowsFallback: (
<div className="c-flex-1 c-w-full absolute inset-0 c-flex c-flex-col c-items-center c-justify-center">

View File

@ -12,8 +12,6 @@ import {
import { MDLocalInternal, MDProps } from "./utils/typings";
import { mdRenderLoop } from "./utils/md-render-loop";
import { parseGenField } from "lib/gen/utils";
import { NDT } from "src/data/timezoneNames";
import { mdBread } from "./utils/md-bread";
export const MasterDetail: FC<MDProps> = (arg) => {
const {
@ -34,10 +32,9 @@ export const MasterDetail: FC<MDProps> = (arg) => {
name,
status: isEditor ? "init" : "ready",
actions: [],
breadcrumb: {
list: [],
header: {
breadcrumb: [],
render: () => {},
reload: () => {}
},
selected: null,
tab: {
@ -83,7 +80,7 @@ export const MasterDetail: FC<MDProps> = (arg) => {
md.status = "ready";
const fields = parseGenField(gen_fields);
const pk = fields.find((e) => e.is_pk);
md.pk = pk
md.pk = pk;
md.params.parse();
if (pk) {
const value = md.params.hash[md.name];
@ -92,13 +89,12 @@ export const MasterDetail: FC<MDProps> = (arg) => {
const tab = md.params.tabs[md.name];
if (tab && md.tab.list.includes(tab)) {
md.tab.active = tab;
}else{
md.tab.active = "detail"
} else {
md.tab.active = "detail";
}
}
}
}
md.breadcrumb.reload();
return (
<div
className={cx(

View File

@ -15,11 +15,11 @@ export const generateTableList = async (
) => {
let table = "" as string;
try {
table = eval(data.gen_table.value);
table = eval(data.gen__table.value);
} catch (e) {
table = data.gen_table.value;
table = data.gen__table.value;
}
const raw_fields = JSON.parse(data.gen_fields.value) as (
const raw_fields = JSON.parse(data.gen__fields.value) as (
| string
| { value: string; checked: string[] }
)[];
@ -53,7 +53,7 @@ export const generateTableList = async (
if (data["opt__on_load"]) {
result.opt__on_load = {
mode: "raw",
value: on_load({ pk, table, select, pks }),
value: on_load({ pk, table, select, pks,fields }),
};
}
let first = true;
@ -91,10 +91,10 @@ export const generateTableList = async (
adv: {
js: `\
<div {...props} className={cx(props.className, "")}>
${arg.mode === "list" ? "{JSON.stringify(row)}" : "<FormatValue value={col.value} name={col.name} gen_fields={gen_fields} ${tree_depth} />"}
${arg.mode === "list" ? "{JSON.stringify(row)}" : `<FormatValue value={col.value} name={col.name} gen_fields={gen__fields} ${tree_depth} />`}
</div>`,
jsBuilt: `\
render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }),React.createElement(FormatValue, { value: col.value, name: col.name, gen_fields: gen_fields, ${tree_depth_built} })));
render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }),React.createElement(FormatValue, { value: col.value, name: col.name, gen_fields: gen__fields, ${tree_depth_built} })));
`,
},
}),

View File

@ -50,7 +50,7 @@ export const generateMDForm = async (
props,
},
};
generateForm(async (props: any) => {}, props, tablelist, false);
generateForm(async (props: any) => {}, props, tablelist, false, true);
tab_detail?.edit.setProp("breadcrumb", {
mode: "raw",
value: `\
@ -93,14 +93,7 @@ export const generateMDForm = async (
}
`
})
console.log({form: {
type: "item",
name: "item",
component: {
id: "c4e65c26-4f36-48aa-a5b3-0771feac082e",
props,
},
}})
tab_detail?.edit.setChilds([
{
type: "item",

View File

@ -36,5 +36,4 @@ export const generateMasterDetail: GenFn<{ item: PrasiItem, table: string, field
//
// }
await item.edit.commit();
console.log({item})
};

View File

@ -1,7 +1,7 @@
import { formatName } from "lib/comps/form/gen/fields";
import { createItem } from "lib/gen/utils";
import get from "lodash.get";
import { generateTableList } from "./gen-table-list";
import { formatName } from "lib/comps/form/gen/fields";
export const generateList = async (
arg: { item: PrasiItem; table: string; fields: any },
@ -13,7 +13,7 @@ export const generateList = async (
(e) => get(e, "component.id") === "c68415ca-dac5-44fe-aeb6-936caf8cc491"
);
const props: Record<string, PropVal> = {
gen_table: {
gen__table: {
mode: "string",
value: `"${arg.table}"`,
},
@ -77,7 +77,7 @@ rows: any[];
idx: any;
}`,
},
gen_fields: {
gen__fields: {
mode: "raw",
value: `${JSON.stringify(arg.fields)}`,
},
@ -117,22 +117,17 @@ idx: any;
url?: string;
onClick?: () => void;
}
`
})
console.log({
type: "item",
name: "item",
component: {
id: "567d5362-2cc8-4ca5-a531-f771a5c866c2",
props,
`,
});
tab_master?.edit.setChilds([
{
type: "item",
name: "item",
component: {
id: "567d5362-2cc8-4ca5-a531-f771a5c866c2",
props,
},
},
})
tab_master?.edit.setChilds([ {
type: "item",
name: "item",
component: {
id: "567d5362-2cc8-4ca5-a531-f771a5c866c2",
props,
},
}]);
]);
};

View File

@ -8,13 +8,22 @@ export const generateSelect = (data: Array<any>) => {
select: {},
};
for (const r of f.relation.fields) {
select[f.name].select[r.name] = true;
if (r.type === "has-one") {
select[f.name].select[r.name] = { select: {} };
for (const rel of r.relation.fields) {
select[f.name].select[r.name].select[rel.name] = true;
}
} else {
select[f.name].select[r.name] = true;
}
}
}
if (f.is_pk) {
pk = f.name;
}
}
return {
pk,
select,

View File

@ -1,44 +1,67 @@
export const on_load = ({
pk,
table,
select,
pks,
}: {
pk: string;
table: string;
select: any;
pks: Record<string, string>;
}) => {
const sample = {} as any;
pk,
table,
select,
pks,
fields,
}: {
pk: string;
table: string;
select: any;
pks: Record<string, string>;
fields: Array<any>;
}) => {
const sample = {} as any;
for (const [k, v] of Object.entries(select) as any) {
if (typeof v === "object") {
sample[k] = {};
Object.keys(v.select)
.filter((e) => e !== pks[k])
.map((e) => {
sample[k][e] = "sample";
});
} else {
sample[k] = "sample";
for (const [k, v] of Object.entries(select) as any) {
if (typeof v === "object") {
const val = {} as any;
Object.keys(v.select)
.filter((e) => e !== pks[k])
.map((e) => {
val[e] = "sample";
});
const field = fields.find((e) => e.name === k);
sample[k] = val;
if(field){
if(field.type === "has-many"){
sample[k] = [val];
}
}
} else {
sample[k] = "sample";
}
}
return `\
return `\
(arg: TableOnLoad) => {
if (isEditor) return [${JSON.stringify(sample)}];
return new Promise(async (done) => {
let where = arg.where;
try {
if (!isEditor)
where = softDeleteFilter(where, {
feature: opt__feature,
field: sft__fields,
type: sft__type,
});
} catch (e) {}
if (arg.mode === 'count') {
return await db.${table}.count();
return await db.${table}.count({
where: {
...where,
}
});
}
const items = await db.${table}.findMany({
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
orderBy: arg.orderBy || {
${pk}: "desc"
},
where: {
...where,
},
...arg.paging,
});
@ -54,5 +77,4 @@ export const on_load = ({
where?: any
}
`;
};
};

View File

@ -15,7 +15,6 @@ export const MDDetail: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
if (!detail) {
return null;
}
console.log(md.tab.list);
return (
<>
{md.props.show_head === "only-child" && <MDHeader md={md} mdr={mdr} />}
@ -116,10 +115,8 @@ export const MDRenderTab: FC<{
}> = ({ child, on_init, breadcrumb }) => {
useEffect(() => {
let md = on_init();
md.breadcrumb.list = breadcrumb();
if (!isEditor) {
md.breadcrumb.reload();
}
md.header.breadcrumb = breadcrumb();
md.header.render();
}, []);
return <>{child}</>;
};

View File

@ -1,8 +1,10 @@
import { FC } from "react";
import { FC, useState } from "react";
import { MDLocal, MDRef } from "../utils/typings";
export const MDHeader: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
const [_, set] = useState({});
const head = mdr.item.edit.props?.header.value;
const PassProp = mdr.PassProp;
md.header.render = () => set({});
return <PassProp md={md}>{head}</PassProp>;
};

View File

@ -10,14 +10,14 @@ export const MDRenderMaster: FC<{
min_size: any;
child: any;
on_init: () => MDLocal;
breadcrumb: () => Array<any>
breadcrumb: () => Array<any>;
}> = ({ child, on_init, min_size, size, breadcrumb }) => {
useEffect(() => {
console.log("master");
let md = on_init();
md.breadcrumb.list = breadcrumb();
if(!isEditor){
md.breadcrumb.reload();
}
md.header.breadcrumb = breadcrumb();
md.header.render();
if (md) {
let width = 0;
let min_width = 0;
@ -33,8 +33,8 @@ export const MDRenderMaster: FC<{
md.panel.min_size = min_width;
md.panel.size = width;
}
};
}, [])
}
}, []);
return <>{child}</>;
};

View File

@ -19,7 +19,7 @@ export const editorMDInit = (md: MDLocal, mdr: MDRef, arg: MDProps) => {
md.props.gen_table = gen_table;
md.props.on_init = on_init;
if (!mdr.master || (mdr.master && !get(mdr, "master.edit.childs.0.childs.length"))) {
md.breadcrumb.list = [
md.header.breadcrumb = [
{
label: (
<>
@ -39,7 +39,7 @@ export const editorMDInit = (md: MDLocal, mdr: MDRef, arg: MDProps) => {
];
md.status = "unready";
} else {
md.breadcrumb.list = [];
md.header.breadcrumb = [];
md.status = "ready";
}
};

View File

@ -1,43 +0,0 @@
import get from "lodash.get";
import { MDLocal, MDProps, MDRef } from "./typings";
import { getProp } from "lib/utils/get-prop";
export const mdBread = (md: MDLocal, mdr: MDRef, props: MDProps, item: PrasiItem) => {
const child = get(item, "component.props.child.content.childs") || [];
const master = child.find((e) => get(e, "component.id")=== "c68415ca-dac5-44fe-aeb6-936caf8cc491")
let mode = "master";
if (isEditor) {
mode = md.props.editor_tab;
} else {
if (typeof md.selected === "object") {
if (!md.tab.active) {
md.tab.active = md.tab.list[0];
}
mode = md.tab.active;
}
}
const done = (item: Array<any>) => {
md.breadcrumb.list = item || [];
}
if(mode === "master"){
const master_bread = getProp(master, "breadcrumb", { md });
if (master_bread instanceof Promise) {
master_bread.then((e) => {
done(e)
});
} else {
done(master_bread)
}
}else{
const tab = child.find((e) => get(e, "component.id")=== "cb52075a-14ab-455a-9847-6f1d929a2a73" && eval(get(e, "component.props.name.value")) === mode)
const master_bread = getProp(tab, "breadcrumb", { md });
if (master_bread instanceof Promise) {
master_bread.then((e) => {
done(e)
});
} else {
done(master_bread)
}
}
console.log({mode})
};

View File

@ -34,7 +34,7 @@ export type MDActions = {
export type MDLocalInternal = {
name: string;
status: "init" | "unready" | "ready";
breadcrumb: {list:BreadItem[], render: () => void, reload: () => void};
header: { breadcrumb: BreadItem[]; render: () => void };
actions: MDActions;
selected: any;
tab: {
@ -89,11 +89,13 @@ export type MDLocal = MDLocalInternal & { render: (force?: boolean) => void };
export const MasterDetailType = `const md = {
name: string;
status: string;
breadcrumb: {
label: React.ReactNode;
url?: string;
onClick?: () => void;
}[];
header: {
breadcrumb: {
label: React.ReactNode;
url?: string;
onClick?: () => void;
}[]
};
actions: (
{
action?: string;

View File

@ -22,10 +22,11 @@ const buttonVariants = cva(
},
size: {
default: "c-h-10 c-px-4 c-py-2",
xs: "c-h-7 c-rounded-sm c-px-2",
sm: "c-h-9 c-rounded-md c-px-3",
lg: "c-h-11 c-rounded-md c-px-8",
icon: "c-h-10 c-w-10",
nozise: ""
nozise: "",
},
},
defaultVariants: {
@ -36,16 +37,16 @@ const buttonVariants = cva(
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
extends React.ButtonHTMLAttributes<HTMLDivElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
const Button = React.forwardRef<HTMLDivElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
<div
className={cn(
buttonVariants({ variant, size, className }),
`btn-${variant || "default"} btn c-transition-all c-duration-300`
@ -58,10 +59,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
);
Button.displayName = "Button";
const FloatButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
const FloatButton = React.forwardRef<HTMLDivElement, ButtonProps>(
({ variant, className, ...props }, ref) => {
return (
<button
<div
className={cn(
buttonVariants({ variant, className }),
`btn-${

View File

@ -50,7 +50,11 @@ export const TypeaheadOptions: FC<{
`
: css`
min-width: 150px;
`
`,
css`
max-height: 400px;
overflow: auto;
`
)}
>
{options.map((item, idx) => {
@ -75,7 +79,7 @@ export const TypeaheadOptions: FC<{
onSelect?.(item.value);
}}
>
{item.label}
{item.label || <>&nbsp;</>}
</div>
);
})}

View File

@ -5,7 +5,7 @@ import { Badge } from "./badge";
import { TypeaheadOptions } from "./typeahead-opt";
export const Typeahead: FC<{
value?: string[];
value?: string[] | null;
placeholder?: string;
options?: (arg: {
search: string;
@ -90,6 +90,7 @@ export const Typeahead: FC<{
}
useEffect(() => {
if (!value) return;
if (!isEditor) {
if (local.options.length === 0) {
loadOptions().then(() => {
@ -105,6 +106,9 @@ export const Typeahead: FC<{
if (typeof value === "object" && value) {
local.value = value;
local.render();
} else {
local.value = [];
local.render();
}
}
}
@ -299,7 +303,17 @@ export const Typeahead: FC<{
if (local.mode === "single" && local.value.length > 1) {
local.value = [local.value.pop() || ""];
}
const valueLabel = local.value.map((value) => {
if (local.value.length === 0) {
if (local.mode === "single") {
if (!local.open) {
local.select = null;
local.search.input = "";
}
}
}
const valueLabel = local.value?.map((value) => {
const item = local.options.find((item) => item.value === value);
if (local.mode === "single") {
@ -341,7 +355,7 @@ export const Typeahead: FC<{
}
}}
>
<div>{e?.label}</div>
<div>{e?.label || <>&nbsp;</>}</div>
<X size={12} />
</Badge>
);
@ -503,7 +517,7 @@ export const Typeahead: FC<{
<div
className={cx(
"c-absolute c-pointer-events-none c-z-10 c-inset-0 c-left-auto c-flex c-items-center ",
" c-justify-center c-w-6 c-mr-1 c-my-2 c-bg-transparent",
" c-justify-center c-w-6 c-mr-1 c-my-2 c-bg-white",
disabled && "c-hidden"
)}
>

View File

@ -74,6 +74,7 @@ export { prasi_gen } from "@/gen/prasi_gen";
export { password } from "@/utils/password";
export { generateTableList } from "@/comps/md/gen/gen-table-list";
export { generateForm } from "@/comps/form/gen/gen-form";
export { generateSelect } from "@/comps/md/gen/md-select";
/** Session */
export {
@ -81,11 +82,9 @@ export {
RG,
UserSession,
} from "@/preset/login/utils/register";
export { prasi_user } from "@/preset/login/utils/user";
export { Login } from "@/preset/login/Login";
export { logout } from "@/preset/login/utils/logout";
export { generateLogin } from "@/preset/login/utils/generate";
export { select as generateSelect } from "@/preset/login/utils/select";
export { Card } from "@/comps/custom/Card";
@ -114,4 +113,4 @@ export { getPathname } from "./utils/pathname";
export * from "@/comps/ui/typeahead";
export * from "@/comps/ui/input";
export {softDeleteFilter} from "@/utils/soft-delete-filter"
export { softDeleteFilter } from "@/utils/soft-delete-filter";

View File

@ -15,6 +15,7 @@ export const on_submit = ({
return `\
async ({ form, error }: IForm) => {
if (isEditor) return false;
if (typeof form !== "object") return false;
if (typeof error === "object" && Object.keys(error).length > 0) return false;

View File

@ -1,7 +1,8 @@
import { FC, useEffect } from "react";
import { prasi_user } from "./utils/user";
import { loadSession } from "./utils/load";
const w = window as unknown as {
user: any;
prasi_home: Record<string, string>;
};
@ -15,8 +16,11 @@ export const Login: FC<LGProps> = (props) => {
w.prasi_home = props.url_home[0];
useEffect(() => {
try {
const home = prasi_user.prasi_home[prasi_user.user.m_role.name];
navigate(home);
loadSession();
if (w.user) {
const home = w.prasi_home[w.user.m_role.name];
navigate(home);
}
} catch (e: any) {}
}, []);
return <>{props.body}</>;

View File

@ -54,7 +54,8 @@ export const generateLogin = async (
});
if (data_user) {
registerSession({ data: data_user, expired: null });
const home = prasi_user.prasi_home[prasi_user.user.m_role.name];
const w = window as any;
const home = w.prasi_home[w.user.m_role.name];
navigate(home);
}
}else{

View File

@ -4,27 +4,26 @@ import { logout } from "./logout";
const w = window as any;
const parse = parser.exportAsFunctionAny("en-US");
export const loadSession = (url_login?: string) => {
try {
const user = localStorage.getItem("user");
if (user) {
const raw = JSON.parse(user);
w.user = raw.data;
if (typeof raw === "object") {
const session: UserSession = raw;
const expired = parse(session.expired);
if (
typeof expired === "object" &&
expired instanceof Date
) {
if (new Date() > expired) {
logout(url_login);
if (!isEditor) {
try {
const user = localStorage.getItem("user");
if (user) {
const raw = JSON.parse(user);
w.user = raw.data;
if (typeof raw === "object") {
const session: UserSession = raw;
const expired = parse(session.expired);
if (expired instanceof Date) {
if (new Date() > expired) {
if (url_login) logout(url_login);
}
}
}
} else {
if (url_login) logout(url_login);
}
} else {
logout(url_login);
} catch (e) {
if (url_login) logout(url_login);
}
} catch (e) {
logout(url_login);
}
};

View File

@ -14,7 +14,5 @@ export const logout = (url_login?: string) => {
if (localStorage.getItem("user")) {
localStorage.removeItem("user");
}
if (!getPathname().startsWith("/dev")) {
if (url_login) navigate(url_login);
}
if (url_login) navigate(url_login);
};

View File

@ -1,13 +0,0 @@
export const prasi_user = window as unknown as {
user: {
id: string;
username: string;
name: string;
fullname: string;
m_role: {
id: string;
name: string;
};
};
prasi_home: Record<string, string>;
};

View File

@ -32,9 +32,9 @@ type LYTChild = {
desktop?: ReactNode;
tablet?: ReactNode;
child?: ReactNode;
children: ReactNode;
default_layout: ReactNode;
exception?: Array<string>;
defaultLayout: ReactNode;
blank_layout: ReactNode;
};
export const Layout: FC<LYTChild> = (props) => {
@ -64,12 +64,18 @@ export const Layout: FC<LYTChild> = (props) => {
const no_layout = props.exception;
useEffect(() => {
loadSession("/auth/login");
render();
}, []);
if (Array.isArray(no_layout))
if (!isEditor && Array.isArray(no_layout)) {
if (no_layout.length) {
if (no_layout.includes(path)) {
return <>{props.defaultLayout}</>;
return <>{props.blank_layout}</>;
}
}
return <>{props.children}</>;
}
if (!w.user) return props.blank_layout;
return <>{props.default_layout}</>;
};

6
utils/is-empty-string.ts Executable file
View File

@ -0,0 +1,6 @@
export const isEmptyString = (data: any) => {
if(typeof data === 'string'){
return data.trim() === '' || data === "" || !data
}
return typeof data === "undefined"
}

18
utils/soft-del-rel.ts Executable file
View File

@ -0,0 +1,18 @@
import { prasi_gen } from "lib/gen/prasi_gen";
export const sofDeleteField = async (
table: string,
field: string
) => {
const result = {} as any;
const fields =await prasi_gen.prop.fields(table);
const value = fields.find((e: any) => {
const val = JSON.parse(e.value);
if (val.type === "has-one" || val.type === "has-many" || val.is_pk) {
return false;
}
return (e.label === field);
});
if(value) return JSON.parse(value.value)
return result;
};

View File

@ -1,26 +1,28 @@
import { isEmptyString } from "./is-empty-string";
export const softDeleteFilter = (
where: any,
soft: {
field: string;
type: "boolean" | "nullable";
feature: Array<string>;
}
) => {
console.log({ where });
const feature = soft.feature || [];
if (!feature.find((e) => e === "soft_delete")) return where;
const defaultParam = typeof where === "object" ? where : {};
if (isEmptyString(soft.field) || isEmptyString(soft.type))
return defaultParam;
const result = {
AND: [
typeof where === "object"
? { ...defaultParam }
: {
[soft.field]:
soft.type === "boolean"
? true
: {
not: null,
},
},
typeof defaultParam === "object" ? { ...defaultParam } : {},
{
[soft.field]:
soft.type === "boolean"
? false
:null,
},
],
};
console.log(result);
return result;
};