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

View File

@ -7,13 +7,14 @@ import { editorFormData } from "./utils/ed-data";
import { formInit } from "./utils/init"; import { formInit } from "./utils/init";
import { formReload } from "./utils/reload"; import { formReload } from "./utils/reload";
import { getPathname } from "lib/utils/pathname"; import { getPathname } from "lib/utils/pathname";
import { sofDeleteField } from "lib/utils/soft-del-rel";
const editorFormWidth = {} as Record<string, { w: number; f: any }>; const editorFormWidth = {} as Record<string, { w: number; f: any }>;
export { FMLocal } from "./typings"; export { FMLocal } from "./typings";
export const Form: FC<FMProps> = (props) => { export const Form: FC<FMProps> = (props) => {
const { PassProp, body } = props; const { PassProp, body, feature, sfd_field } = props;
const fm = useLocal<FMInternal>({ const fm = useLocal<FMInternal>({
data: editorFormData[props.item.id] data: editorFormData[props.item.id]
? editorFormData[props.item.id].data ? editorFormData[props.item.id].data
@ -51,8 +52,30 @@ export const Form: FC<FMProps> = (props) => {
? editorFormWidth[props.item.id].f ? editorFormWidth[props.item.id].f
: "full", : "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({ const ref = useRef({
el: null as null | HTMLFormElement, el: null as null | HTMLFormElement,
rob: new ResizeObserver(([e]) => { rob: new ResizeObserver(([e]) => {

View File

@ -31,45 +31,7 @@ export const Field: FC<FieldProp> = (arg) => {
const errors = fm.error.get(name); const errors = fm.error.get(name);
const props = { ...arg.props }; const props = { ...arg.props };
delete props.className; 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 ( return (
<label <label
@ -79,8 +41,8 @@ export const Field: FC<FieldProp> = (arg) => {
css` css`
padding: 5px 0px 0px 10px; padding: 5px 0px 0px 10px;
`, `,
w === "auto" && fm.size.field === "full" && "c-w-full", 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 === "full" && "c-w-full",
w === "¾" && "c-w-3/4", w === "¾" && "c-w-3/4",
w === "½" && "c-w-1/2", w === "½" && "c-w-1/2",
@ -111,7 +73,7 @@ export const Field: FC<FieldProp> = (arg) => {
{errors.length > 0 && ( {errors.length > 0 && (
<div <div
className={cx( 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" field.desc && "c-pt-0"
)} )}
> >

View File

@ -18,7 +18,7 @@ export const FieldInput: FC<{
child: any; child: any;
_item: PrasiItem; _item: PrasiItem;
arg: FieldProp; arg: FieldProp;
}> = ({ field, fm, arg, _item, PassProp, child}) => { }> = ({ field, fm, arg, _item, PassProp, child }) => {
// return <></> // return <></>
const prefix = const prefix =
typeof field.prefix === "function" 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") { if (type_field === "multi-option" && arg.sub_type === "table-edit") {
const childsTableEdit = get( const childsTableEdit = get(
_item, _item,
@ -57,7 +59,7 @@ export const FieldInput: FC<{
child: get(_item, "edit.props.child.value") as PrasiItem, child: get(_item, "edit.props.child.value") as PrasiItem,
bottom: childsTableEdit.find((e) => e.name === "bottom") as PrasiItem, bottom: childsTableEdit.find((e) => e.name === "bottom") as PrasiItem,
}; };
return ( table_edit = (
<TableEdit <TableEdit
on_init={() => { on_init={() => {
return fm; return fm;
@ -70,13 +72,38 @@ export const FieldInput: FC<{
body={tableEdit.child} 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 ( return (
<div <div
className={cx( className={cx(
!["toogle", "button", "radio", "checkbox"].includes(arg.sub_type) !["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" fm.status === "loading"
? css` ? css`
@ -115,30 +142,40 @@ export const FieldInput: FC<{
field.disabled && "c-pointer-events-none" field.disabled && "c-pointer-events-none"
)} )}
> >
{type_field === "custom" && arg.custom ? ( {not_ready ? (
<>{custom}</> not_ready
) : ( ) : (
<> <>
{["date", "input"].includes(type_field) ? ( {type_field === "custom" && arg.custom ? (
<FieldTypeInput <>{custom}</>
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} />
) : ( ) : (
<>{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 { TableList } from "lib/comps/list/TableList";
import { useLocal } from "lib/utils/use-local"; import { useLocal } from "lib/utils/use-local";
import { FC } from "react"; import { FC, ReactElement, useEffect, useRef } from "react";
import { BaseForm } from "../../base/BaseForm"; import { BaseForm } from "../../base/BaseForm";
import { FMLocal } from "../../typings"; import { FMLocal } from "../../typings";
import get from "lodash.get";
export const TableEdit: FC<{ export const TableEdit: FC<{
on_init: () => FMLocal; 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 ( return (
<> <>
<div className="c-w-full c-h-full c-flex c-flex-col"> <div className="c-w-full c-h-full c-flex c-flex-col">
@ -32,6 +41,18 @@ export const TableEdit: FC<{
> .rdg { > .rdg {
overflow-y: hidden !important; 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={{ style={{
@ -41,57 +62,82 @@ export const TableEdit: FC<{
? 50 ? 50
: value.length * 50 + 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 <TableList
rowHeight={50} row_height={50}
feature={[]} feature={[]}
child={child} child={child}
PassProp={PassProp} PassProp={PassProp}
name={""} name={""}
value={value} value={value}
on_init={() => {}} on_init={(tbl) => {
local.tbl = tbl;
local.render();
}}
mode={"table"} mode={"table"}
_item={item} _item={item}
gen_fields={[]} gen_fields={[]}
row_click={() => {}} row_click={({ event }) => {
event.preventDefault();
event.stopPropagation();
}}
selected={() => { selected={() => {
return false; return false;
}} }}
filter_name={""} filter_name={""}
render_col={(arg: any) => { render_col={(arg) => {
const { props, tbl, child } = arg; const { props, tbl, child } = arg;
const fm_row = { ...fm }; const fm_row = { ...fm, render: local.render };
fm_row.data = props.row; fm_row.data = props.row;
fm_row.render = fm.render;
local.tbl = tbl; local.tbl = tbl;
const key = props.column.key;
return ( return (
<PassProp <PassProp
idx={props.rowIdx} idx={props.rowIdx}
row={props.row} row={props.row}
col={{ col={{
name: props.column.key, name: key,
value: props.row[props.column.key], value: props.row[props.column.key],
depth: props.row.__depth || 0, depth: props.row.__depth || 0,
}} }}
rows={tbl.data} rows={tbl.data}
fm={fm_row} fm={fm_row}
ext_fm={{ ext_fm={{
change: () => {},
remove: () => { remove: () => {
if (Array.isArray(fm.data[name])) { fm.data[name] = tbl.data.filter(
fm.data[name] = value.filter( (e: any) => e !== props.row
(e: any) => e !== props.row );
); fm.render();
fm.render();
tbl.data = fm.data[name];
tbl.render();
}
}, },
add: () => { add: () => {
if (Array.isArray(value)) { tbl.data.push({});
value.push({}); fm.render();
} else {
alert("value bukan array");
}
}, },
}} }}
> >
@ -99,35 +145,23 @@ export const TableEdit: FC<{
</PassProp> </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> </div>
<PassProp <PassProp
ext_fm={{ ext_fm={{
add: () => { add: () => {
if (Array.isArray(value)) { local.tbl.data.push({});
value.push({}); fm.render();
fm.data[name] = value;
fm.render(); setTimeout(() => {
} else { const last = Array.from(
fm.data[name] = [{}]; ref.current?.querySelectorAll(".rdg-row") || []
fm.render(); ).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 { 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 { FieldLoading } from "lib/comps/ui/field-loading";
import { Typeahead } from "lib/comps/ui/typeahead"; import { Typeahead } from "lib/comps/ui/typeahead";
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
export const TypeDropdown: FC<{ export const TypeDropdown: FC<{
field: FieldLocal; field: FieldLocal;
@ -11,7 +11,7 @@ export const TypeDropdown: FC<{
}> = ({ field, fm, arg }) => { }> = ({ field, fm, arg }) => {
const local = useLocal({ const local = useLocal({
loaded: false, loaded: false,
options: [], options: [] as { value: string; label: string; data: any }[],
}); });
let value = let value =
typeof arg.opt_get_value === "function" typeof arg.opt_get_value === "function"
@ -22,6 +22,7 @@ export const TypeDropdown: FC<{
type: field.type, type: field.type,
}) })
: fm.data[field.name]; : fm.data[field.name];
useEffect(() => { useEffect(() => {
if (typeof arg.on_load === "function") { if (typeof arg.on_load === "function") {
const options = arg.on_load({}); const options = arg.on_load({});
@ -38,6 +39,22 @@ export const TypeDropdown: FC<{
} else { } else {
local.options = res; 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.loaded = true;
local.render(); local.render();
}); });
@ -48,12 +65,16 @@ export const TypeDropdown: FC<{
} }
} }
}, []); }, []);
if (!local.loaded) return <FieldLoading />; if (!local.loaded) return <FieldLoading />;
if (field.type === "single-option") if (field.type === "single-option") {
if (value === null) {
fm.data[field.name] = undefined;
}
return ( return (
<> <>
<Typeahead <Typeahead
value={value} value={Array.isArray(value) ? value : [value]}
onSelect={({ search, item }) => { onSelect={({ search, item }) => {
if (item) { if (item) {
arg.opt_set_value({ arg.opt_set_value({
@ -77,6 +98,7 @@ export const TypeDropdown: FC<{
/> />
</> </>
); );
}
return ( return (
<> <>

View File

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

View File

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

View File

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

View File

@ -14,27 +14,27 @@ export const FieldUpload: FC<{
value: 0 as any, value: 0 as any,
display: false as any, display: false as any,
ref: null as any, ref: null as any,
drop: false as boolean drop: false as boolean,
}); });
let display: any = null; let display: any = null;
return ( return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full"> <div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
<div <div
onDrop={(e: any) => { onDrop={(e: any) => {
console.log({ e });
e.preventDefault(); e.preventDefault();
input.drop = false; input.drop = false;
input.render(); input.render();
}} }}
onDragOver={(e:any) => { onDragOver={(e: any) => {
console.log("File(s) in drop zone");
// Prevent default behavior (Prevent file from being opened) // Prevent default behavior (Prevent file from being opened)
e.preventDefault(); e.preventDefault();
input.drop = true; input.drop = true;
input.render(); 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"> <div className="c-flex-row c-flex c-flex-grow c-space-x-2">
<svg <svg

View File

@ -1,16 +1,13 @@
import { generateSelect } from "lib/comps/md/gen/md-select"; 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 { createItem, parseGenField } from "lib/gen/utils";
import capitalize from "lodash.capitalize"; 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 { gen_label } from "./gen-label";
import { get_value } from "./get-value"; import { generateRelation } from "./gen-rel";
import { set_value } from "./set-value";
import get from "lodash.get";
import { gen_rel_many } from "./gen-rel-many"; 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 = { export type GFCol = {
name: string; name: string;
type: string; type: string;
@ -22,9 +19,9 @@ export type GFCol = {
fields: GFCol[]; fields: GFCol[];
}; };
}; };
export const newField = ( export const newField = async (
arg: GFCol, arg: GFCol,
opt: { parent_table: string; value: Array<string> }, opt: { parent_table: string; value: Array<string>; on_change?: string },
show_label: boolean show_label: boolean
) => { ) => {
let show = typeof show_label === "boolean" ? show_label : true; let show = typeof show_label === "boolean" ? show_label : true;
@ -43,6 +40,9 @@ export const newField = (
child: { child: {
childs: [], childs: [],
}, },
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
}, },
}, },
}); });
@ -59,6 +59,9 @@ export const newField = (
child: { child: {
childs: [], 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", ext__show_label: show ? "y" : "n",
type: "single-option", type: "single-option",
sub_type: "toogle", sub_type: "toogle",
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
}, },
}, },
}); });
@ -89,6 +95,9 @@ export const newField = (
child: { child: {
childs: [], childs: [],
}, },
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
}, },
}, },
}); });
@ -102,6 +111,12 @@ export const newField = (
pks: {}, pks: {},
}); });
if (["has-one"].includes(arg.type)) { 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({ return createItem({
component: { component: {
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67", id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
@ -112,6 +127,7 @@ export const newField = (
ext__show_label: show ? "y" : "n", ext__show_label: show ? "y" : "n",
sub_type: "dropdown", sub_type: "dropdown",
rel__gen_table: arg.name, rel__gen_table: arg.name,
rel__gen_fields: [rel__gen_fields, rel__gen_fields],
opt__on_load: [load], opt__on_load: [load],
opt__label: [ opt__label: [
gen_label({ gen_label({
@ -137,6 +153,9 @@ export const newField = (
child: { child: {
childs: [], childs: [],
}, },
ext__on_change: opt.on_change
? [opt.on_change, opt.on_change]
: undefined,
}, },
}, },
}); });
@ -146,48 +165,54 @@ export const newField = (
arg, arg,
rel: fields, rel: fields,
}); });
if (result.on_load) {
return createItem({ let child: any = { childs: [] };
component: { let rel__gen_fields: any = undefined;
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67", let sub_type = "checkbox";
props: { if (arg.relation?.fields?.length > 1) {
name: arg.name, sub_type = "table-edit";
label: formatName(arg.name), rel__gen_fields = JSON.stringify(
type: "multi-option", arg.relation?.fields.map((e) => {
sub_type: "checkbox", const v = (e as any).value;
rel__gen_table: arg.name, return v;
opt__on_load: [result.on_load], })
ext__show_label: show ? "y" : "n", );
opt__label: [result.get_label], child = createItem({
opt__get_value: [result.get_value], childs: await generateRelation(
opt__set_value: [result.set_value], {
child: { rel__gen_fields: { value: rel__gen_fields },
childs: [], rel__gen_table: { value: JSON.stringify(arg.name) },
}, sub_type: { value: "'table-edit'" },
}, },
}, createItem({}),
}); false
} 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: [],
},
},
},
}); });
} }
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 { } else {
// type not found, // type not found,
@ -203,6 +228,9 @@ export const newField = (
child: { child: {
childs: [], 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, modify: (data: any) => void,
data: any, data: any,
item: PrasiItem, 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 ( const raw_fields = JSON.parse(data.gen__fields.value) as (
| string | string
| { value: string; checked: string[] } | { value: string; checked: string[] }
@ -34,280 +40,341 @@ export const generateForm = async (
if (data["on_load"]) { if (data["on_load"]) {
result.on_load = { result.on_load = {
mode: "raw", 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"]) { if (data["on_submit"]) {
result.on_submit = { result.on_submit = {
mode: "raw", mode: "raw",
value: `\ value: `\
async ({ form, error }: IForm) => { async ({ form, error, fm }: IForm) => {
let result = false; let result = false;
try { try {
if (typeof md !== "undefined") {
const data = { ...form }; // data form fm.status = "saving";
const data_rel = ${JSON.stringify( md.render();
rel_many }
)} // list relasi has many
const data_master = {} as Record<string, any> | any; // variabel untuk data master const data = { ...form }; // data form
const data_array = [] as Array<{ const data_rel = ${JSON.stringify(rel_many)} // list relasi has many
table: string; const data_master = {} as Record<string, any> | any; // variabel untuk data master
data: Array<any>; const data_array = [] as Array<{
fk: string; table: string;
}>; // variabel untuk data array atau has many data: Array<any>;
fk: string;
}>; // variabel untuk data array atau has many
// proses untuk membagi antara data master dengan data array // proses untuk membagi antara data master dengan data array
// data array / has many dilihat dari value yang berupa array // data array / has many dilihat dari value yang berupa array
for (const [k, v] of Object.entries(data) as any) { for (const [k, v] of Object.entries(data) as any) {
if (Array.isArray(v)) { if (Array.isArray(v)) {
const rel = Array.isArray(data_rel) && data_rel.length ? data_rel.find((e) => e.table === k) : null const rel = Array.isArray(data_rel) && data_rel.length ? data_rel.find((e) => e.table === k) : null
if (rel) { if (rel) {
data_array.push({ data_array.push({
table: k, table: k,
data: v, data: v,
fk: rel.fk, fk: rel.fk,
}); });
} }
} else { } else {
data_master[k] = v; data_master[k] = v;
} }
} }
// hapus id dari data_master jika ada // hapus id dari data_master jika ada
try { try {
delete data_master.${pk}; delete data_master.${pk};
} catch (ex) {} } catch (ex) {}
if (form.${pk}) { if (form.${pk}) {
await db.${table}.update({ await db.${table}.update({
where: { 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}, ${pk}: form.${pk},
}, },
data: data_master, },
}); };
} else { });
const res = await db.${table}.create({ await db._batch.upsert({
data: data_master, table: current.table,
}); where: {
if (res) form.${pk} = res.${pk}; [current.fk]: form.${pk},
} },
if (data_array.length) { data: data,
const exec_query_bulk = async ( mode: "relation",
current: { table: string; data: Array<any>; fk: string }, } as any);
list: Array<{ table: string; data: Array<any>; fk: string }>,
index: number, if (list.length > 1) {
) => { try {
if (list.length) { index++;
const data = current.data.map((e) => { if (index < list.length - 1) {
return { await exec_query_bulk(list[index], list, index);
...e, }
${table}: { } catch (ex) {}
connect: { }
${pk}: form.${pk}, }
}, };
}, await exec_query_bulk(data_array[0], data_array, 0);
}; }
}); result = true;
await db._batch.upsert({ } catch (e) {
table: current.table, console.error(e);
where: { result = false;
[current.fk]: form.${pk}, }
},
data: data, if (typeof md !== "undefined") {
mode: "relation", fm.status = "ready";
} as any); md.render();
}
if (list.length > 1) {
try { return result;
index++; };
if (index < list.length - 1) {
await exec_query_bulk(list[index], list, index); type IForm = { form: any; error: Record<string, string>; fm: FMLocal }
} `,
} catch (ex) {} };
} }
} if (typeof is_md === "boolean" && is_md) {
}; result.on_init = {
await exec_query_bulk(data_array[0], data_array, 0); mode: "raw",
} value: `\
result = true; ({ submit, reload, fm }: Init) => {
} catch (e) { // on init
console.error(e); if (!isEditor) {
result = false; if (typeof md === "object") {
md.childs["form"] = {
fm: fm
};
}
} }
return result;
}; };
type IForm = { form: any; error: Record<string, string> } type Init = { submit: () => Promise<boolean>; reload: () => void; fm: FMLocal }
`, `,
}; };
} }
const childs = []; const childs = [];
console.log({fields})
for (const item of fields.filter((e) => !e.is_pk)) { for (const item of fields.filter((e) => !e.is_pk)) {
let value = [] as Array<string>; let value = [] as Array<string>;
if (["has-one", "has-many"].includes(item.type)) { if (["has-one", "has-many"].includes(item.type)) {
value = get(item, "value.checked") as any; 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(field);
} }
childs.push({ if (typeof is_md === "boolean" && !is_md)
id: createId(), childs.push({
dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" }, id: createId(),
name: "submit", dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" },
type: "item", name: "submit",
childs: [ type: "item",
{ childs: [
id: createId(), {
dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" }, id: createId(),
name: "bottom", dim: { h: "fit", w: "full", hUnit: "px", wUnit: "px" },
type: "item", name: "bottom",
childs: [ type: "item",
{ childs: [
id: createId(), {
adv: { id: createId(),
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>', adv: {
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}", 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>',
jsBuilt: 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}",
'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', 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", dim: { h: "full", w: "full" },
type: "item", name: "button",
childs: [], type: "item",
mobile: { linktag: {} }, childs: [],
script: { mobile: { linktag: {} },
props: { script: {
size: { value: ' "default";\n' }, props: {
variant: { value: ' "primary";\n' }, size: { value: ' "default";\n' },
on_click: { variant: { value: ' "primary";\n' },
value: on_click: {
" (e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n};\n", value:
" (e) => {\n e.preventDefault();\n e.stopPropagation();\n fm.submit();\n};\n",
},
}, },
}, },
}, component: {
component: { id: "a15d152d-0118-408f-89f1-f6b2dfbd2e05",
id: "a15d152d-0118-408f-89f1-f6b2dfbd2e05", props: {
props: { size: {
size: { idx: 5,
idx: 5, meta: {
meta: { type: "option",
type: "option", options: '["default", "sm", "lg", "icon","nosize"]',
options: '["default", "sm", "lg", "icon","nosize"]', optionsBuilt:
optionsBuilt: ' ["default", "sm", "lg", "icon", "nosize"];\n',
' ["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',
}, },
dim: { h: "full", w: "full" }, name: "prop_5",
name: "label", type: "string",
type: "item", value: '"default"',
childs: [ valueBuilt: ' "default";\n',
{ },
id: createId(), label: {
name: "Wrapped", idx: 1,
type: "item", meta: { type: "content-element" },
childs: [ name: "prop_1",
{ type: "string",
id: createId(), value: '"hello"',
adv: { content: {
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>', id: createId(),
css: "", adv: {
jsBuilt: js: '<div {...props} className={cx(props.className, "")}>\n {children}\n</div>',
'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', css: "",
}, jsBuilt:
dim: { h: "full", w: "full" }, 'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, children));\n',
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",
},
}, },
], dim: { h: "full", w: "full" },
hidden: false, name: "label",
layout: { type: "item",
dir: "col", childs: [
gap: 0, {
wrap: "flex-nowrap", id: createId(),
align: "center", 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,
variant: { meta: {
idx: 3, type: "option",
meta: { options:
type: "option", '["primary", "secondary", "outline", "ghost", "link", "destructive"]',
options: option_mode: "button",
'["primary", "secondary", "outline", "ghost", "link", "destructive"]', optionsBuilt:
option_mode: "button", ' ["primary", "secondary", "outline", "ghost", "link", "destructive"];\n',
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) { if (commit) {
Object.keys(result).map((e) => { Object.keys(result).map((e) => {
item.edit.setProp(e, result[e]); item.edit.setProp(e, result[e]);
@ -316,12 +383,13 @@ export const generateForm = async (
mode: "jsx", mode: "jsx",
value: createItem({ value: createItem({
name: "item", name: "item",
...body_prop,
childs: childs, childs: childs,
}), }),
}); });
await item.edit.commit(); await item.edit.commit();
} else { } else {
console.log({ data }); set(data, "body.value", { ...data.body?.value, ...body_prop });
set(data, "body.value.childs", childs); set(data, "body.value.childs", childs);
Object.keys(result).map((e) => { Object.keys(result).map((e) => {
set(data, e, result[e]); set(data, e, result[e]);

View File

@ -16,20 +16,29 @@ export const gen_label = ({
} }
return `\ return `\
(row: { value: string; label: string; item?: any }) => { (row: { value: string; label: string; data?: any }) => {
const cols = ${JSON.stringify(cols)}; const cols = ${JSON.stringify(cols)};
if (isEditor) { if (isEditor) {
return row.label; return row.label;
} }
const result = []; const result = [];
if (!!row.item && !Array.isArray(row.item)) { if (!!row.data && !row.label && !Array.isArray(row.data)) {
cols.map((e) => { if(cols.length > 0){
if (row.item[e]) { cols.map((e) => {
result.push(row.item[e]); if (row.data[e]) {
} result.push(row.data[e]);
}); }
return result.join(" - "); });
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; return row.label;
} }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
export const get_value = ({ export const get_value = ({
pk, pk,
table, table,
select select,
}: { }: {
pk: string; pk: string;
table: string; table: string;
@ -15,30 +15,32 @@ export const get_value = ({
} }
} }
return `\ return `\
(arg: { (arg: {
options: { label: string; value: string; item?: string }[]; options: { label: string; value: string; item?: string }[];
fm: FMLocal; fm: FMLocal;
name: string; name: string;
type: string; type: string;
}) => { }) => {
const { options, fm, name, type } = arg; const { options, fm, name, type } = arg;
if(isEditor){ if(isEditor){
return fm.data[name]; 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; select: any;
pks: Record<string, string>; pks: Record<string, string>;
opt?: { opt?: {
before_load: string; before_load?: string;
after_load: string; after_load?: string;
}; };
}) => { }) => {
const sample: any = {}; 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 item = {};
let where = {
${pk}: id,
};
if (id){ if (id){
item = await db.${table}.findFirst({ const table = db[gen__table] as any;
where: { const fields = parseGenField(gen__fields);
${pk}: id,
}, const gen = generateSelect(fields);
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")}, item = await table?.findFirst({
where,
select: gen.select,
}); });
${opt?.after_load ? opt?.after_load : ""} ${opt?.after_load ? opt?.after_load : ""}

View File

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

View File

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

View File

@ -18,6 +18,8 @@ export type FMProps = {
gen_fields: any; gen_fields: any;
gen_table: string; gen_table: string;
on_load_deps?: any[]; on_load_deps?: any[];
feature?: any[];
sfd_field?: any;
}; };
export type GenField = export type GenField =
@ -87,7 +89,8 @@ export type FieldProp = {
placeholder: string; placeholder: string;
show_label: boolean; show_label: boolean;
msg_error: string; msg_error: string;
gen_table: string; gen_table?: string;
gen_fields?: string;
}; };
export type FMInternal = { export type FMInternal = {
@ -125,6 +128,9 @@ export type FMInternal = {
height: number; height: number;
field: "full" | "half"; field: "full" | "half";
}; };
soft_delete: {
field: any;
};
}; };
export type FMLocal = FMInternal & { render: () => void }; export type FMLocal = FMInternal & { render: () => void };
@ -150,6 +156,7 @@ export type FieldInternal<T extends FieldProp["type"]> = {
options: { options: {
on_load?: () => Promise<{ value: string; label: string }[]>; on_load?: () => Promise<{ value: string; label: string }[]>;
}; };
on_change?: (arg: { value: any, name: string, fm: FMLocal }) => void | Promise<void>;
prop?: any; prop?: any;
}; };
export type FieldLocal = FieldInternal<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; const { on_load, sonar } = fm.props;
fm.error = formError(fm); fm.error = formError(fm);
fm.field_def = {}; fm.field_def = {};
const defs = parseGenField(fm.props.gen_fields); const defs = parseGenField(fm.props.gen_fields);
for (const d of defs) { for (const d of defs) {
@ -110,7 +109,7 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
}; };
if (typeof fm.props.on_submit === "function") { if (typeof fm.props.on_submit === "function") {
if (fm.props.sonar === "on") { if (fm.props.sonar === "on" && !isEditor) {
toast.loading( toast.loading(
<> <>
<Loader2 className="c-h-4 c-w-4 c-animate-spin" /> <Loader2 className="c-h-4 c-w-4 c-animate-spin" />
@ -126,7 +125,7 @@ export const formInit = (fm: FMLocal, props: FMProps) => {
toast.dismiss(); toast.dismiss();
done_all(success); done_all(success);
if (fm.props.sonar === "on") { if (fm.props.sonar === "on" && !isEditor) {
setTimeout(() => { setTimeout(() => {
toast.dismiss(); toast.dismiss();

View File

@ -33,6 +33,7 @@ export const useField = (
required: required === "y", required: required === "y",
required_msg: arg.required_msg, required_msg: arg.required_msg,
disabled: arg.disabled === "y", disabled: arg.disabled === "y",
on_change: arg.on_change,
}; };
if (field.status === "init" || isEditor) { 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 { 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 { ChangeEvent, FC, MouseEvent, ReactNode, useEffect } from "react"; import {
ChangeEvent,
FC,
MouseEvent,
ReactElement,
ReactNode,
useEffect,
} from "react";
import DataGrid, { import DataGrid, {
ColumnOrColumnGroup, ColumnOrColumnGroup,
RenderCellProps,
Row, Row,
SELECT_COLUMN_KEY, SELECT_COLUMN_KEY,
SortColumn, SortColumn,
} from "react-data-grid"; } from "react-data-grid";
import "./TableList.css";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { Toaster, toast } from "sonner"; import { Toaster, toast } from "sonner";
import { Skeleton } from "../ui/skeleton";
import { sortTree } from "./utils/sort-tree";
import { filterWhere } from "../filter/utils/filter-where"; import { filterWhere } from "../filter/utils/filter-where";
import { Skeleton } from "../ui/skeleton";
import "./TableList.css";
import { sortTree } from "./utils/sort-tree";
type OnRowClick = (arg: { type OnRowClick = (arg: {
row: any; row: any;
@ -38,7 +46,6 @@ type TableListProp = {
}) => Promise<any[]>; }) => Promise<any[]>;
on_init: (arg?: any) => any; on_init: (arg?: any) => any;
mode: "table" | "list" | "grid" | "auto"; mode: "table" | "list" | "grid" | "auto";
// _meta: Record<string, any>;
_item: PrasiItem; _item: PrasiItem;
gen_fields: string[]; gen_fields: string[];
row_click: OnRowClick; row_click: OnRowClick;
@ -47,10 +54,16 @@ type TableListProp = {
feature?: Array<any>; feature?: Array<any>;
filter_name: string; filter_name: string;
render_row?: (child: any, data: any) => ReactNode; render_row?: (child: any, data: any) => ReactNode;
rowHeight?: number; row_height?: number;
render_col?: (props: any) => ReactNode; render_col?: (arg: {
soft_delete_field: string; 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 w = window as any;
const selectCellClassname = css` const selectCellClassname = css`
@ -77,11 +90,12 @@ export const TableList: FC<TableListProp> = ({
feature, feature,
filter_name, filter_name,
render_row, render_row,
rowHeight, row_height: rowHeight,
render_col, 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"); const whereQuery = filterWhere("hello");
if (mode === "auto") { if (mode === "auto") {
if (w.isMobile) { if (w.isMobile) {
@ -124,13 +138,13 @@ export const TableList: FC<TableListProp> = ({
} }
}, },
}, },
cached_row: new WeakMap<any, ReactElement>(),
sort: { sort: {
columns: [] as SortColumn[], columns: [] as SortColumn[],
on_change: (cols: SortColumn[]) => { on_change: (cols: SortColumn[]) => {
if (feature?.find((e) => e === "sorting")) { if (feature?.find((e) => e === "sorting")) {
local.sort.columns = cols; local.sort.columns = cols;
local.paging.skip = 0; local.paging.skip = 0;
if (cols.length > 0) { if (cols.length > 0) {
const { columnKey, direction } = cols[0]; const { columnKey, direction } = cols[0];
@ -179,14 +193,18 @@ export const TableList: FC<TableListProp> = ({
"asc" | "desc" | Record<string, "asc" | "desc"> "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 // code ini digunakan untuk mengambil nama dari pk yang akan digunakan sebagai key untuk id
const pk = local.pk?.name || "id"; const pk = local.pk?.name || "id";
useEffect(() => { useEffect(() => {
if (isEditor) return; if (isEditor || value) {
on_init(local);
return;
}
(async () => { (async () => {
on_init(local); on_init(local);
if (local.status === "reload" && typeof on_load === "function") { if (local.status === "reload" && typeof on_load === "function") {
@ -198,7 +216,10 @@ export const TableList: FC<TableListProp> = ({
async reload() {}, async reload() {},
where, where,
orderBy, 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) { if (id_parent) {
load_args.paging = {}; load_args.paging = {};
@ -218,6 +239,7 @@ export const TableList: FC<TableListProp> = ({
} }
})(); })();
}, [local.status, on_load, local.sort.orderBy]); }, [local.status, on_load, local.sort.orderBy]);
const raw_childs = get( const raw_childs = get(
child, child,
"props.meta.item.component.props.child.content.childs" "props.meta.item.component.props.child.content.childs"
@ -341,7 +363,7 @@ export const TableList: FC<TableListProp> = ({
} }
for (const child of childs) { for (const child of childs) {
let key = getProp(child, "name", {}); let 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({
@ -352,7 +374,12 @@ export const TableList: FC<TableListProp> = ({
sortable: true, sortable: true,
renderCell(props) { renderCell(props) {
if (typeof render_col === "function") if (typeof render_col === "function")
return render_col({ props, tbl: local, child }); return render_col({
props,
tbl: local,
child,
});
return ( return (
<PassProp <PassProp
idx={props.rowIdx} idx={props.rowIdx}
@ -389,9 +416,6 @@ export const TableList: FC<TableListProp> = ({
</>, </>,
{ {
dismissible: true, dismissible: true,
className: css`
background: #e4f7ff;
`,
} }
); );
} else { } 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 (isEditor && local.status !== "ready") {
if (local.data.length === 0) { if (local.data.length === 0) {
const load_args: any = { const load_args: any = {
async reload() {}, async reload() {},
where, 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 (id_parent) load_args.paging = {};
if (typeof on_load === "function") { if (typeof on_load === "function") {
local.data = on_load({ ...load_args, mode: "query" }) as any; local.data = on_load({ ...load_args, mode: "query" }) as any;
@ -430,9 +459,7 @@ export const TableList: FC<TableListProp> = ({
} }
local.status = "ready"; local.status = "ready";
} }
}
let selected_idx = -1;
let data = local.data || []; let data = local.data || [];
if (id_parent && local.pk && local.sort.columns.length === 0) { if (id_parent && local.pk && local.sort.columns.length === 0) {
@ -498,12 +525,20 @@ export const TableList: FC<TableListProp> = ({
columns={columns} columns={columns}
rows={data} rows={data}
onScroll={local.paging.scroll} onScroll={local.paging.scroll}
selectedRows={new Set() as ReadonlySet<any>}
onSelectedCellChange={() => {}}
onSelectedRowsChange={() => {}}
renderers={ renderers={
local.status !== "ready" local.status !== "ready"
? undefined ? undefined
: { : {
renderRow(key, props) { 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({ const isSelect = selected({
idx: props.rowIdx, idx: props.rowIdx,
row: props.row, row: props.row,
@ -533,16 +568,10 @@ export const TableList: FC<TableListProp> = ({
)} )}
/> />
); );
if (typeof render_row === "function") { if (cache_row) {
return render_row(child_row, props.row); local.cached_row.set(props.row, child_row);
} }
// return child_row; return child_row;
return (
<>
<div className="c-contents">{child_row}</div>
</>
);
}, },
noRowsFallback: ( noRowsFallback: (
<div className="c-flex-1 c-w-full absolute inset-0 c-flex c-flex-col c-items-center c-justify-center"> <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 { MDLocalInternal, MDProps } from "./utils/typings";
import { mdRenderLoop } from "./utils/md-render-loop"; import { mdRenderLoop } from "./utils/md-render-loop";
import { parseGenField } from "lib/gen/utils"; import { parseGenField } from "lib/gen/utils";
import { NDT } from "src/data/timezoneNames";
import { mdBread } from "./utils/md-bread";
export const MasterDetail: FC<MDProps> = (arg) => { export const MasterDetail: FC<MDProps> = (arg) => {
const { const {
@ -34,10 +32,9 @@ export const MasterDetail: FC<MDProps> = (arg) => {
name, name,
status: isEditor ? "init" : "ready", status: isEditor ? "init" : "ready",
actions: [], actions: [],
breadcrumb: { header: {
list: [], breadcrumb: [],
render: () => {}, render: () => {},
reload: () => {}
}, },
selected: null, selected: null,
tab: { tab: {
@ -82,8 +79,8 @@ export const MasterDetail: FC<MDProps> = (arg) => {
} else { } else {
md.status = "ready"; md.status = "ready";
const fields = parseGenField(gen_fields); const fields = parseGenField(gen_fields);
const pk = fields.find((e) => e.is_pk); const pk = fields.find((e) => e.is_pk);
md.pk = pk md.pk = pk;
md.params.parse(); md.params.parse();
if (pk) { if (pk) {
const value = md.params.hash[md.name]; const value = md.params.hash[md.name];
@ -92,13 +89,12 @@ export const MasterDetail: FC<MDProps> = (arg) => {
const tab = md.params.tabs[md.name]; const tab = md.params.tabs[md.name];
if (tab && md.tab.list.includes(tab)) { if (tab && md.tab.list.includes(tab)) {
md.tab.active = tab; md.tab.active = tab;
}else{ } else {
md.tab.active = "detail" md.tab.active = "detail";
} }
} }
} }
} }
md.breadcrumb.reload();
return ( return (
<div <div
className={cx( className={cx(

View File

@ -15,11 +15,11 @@ export const generateTableList = async (
) => { ) => {
let table = "" as string; let table = "" as string;
try { try {
table = eval(data.gen_table.value); table = eval(data.gen__table.value);
} catch (e) { } 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 | string
| { value: string; checked: string[] } | { value: string; checked: string[] }
)[]; )[];
@ -53,7 +53,7 @@ export const generateTableList = async (
if (data["opt__on_load"]) { if (data["opt__on_load"]) {
result.opt__on_load = { result.opt__on_load = {
mode: "raw", mode: "raw",
value: on_load({ pk, table, select, pks }), value: on_load({ pk, table, select, pks,fields }),
}; };
} }
let first = true; let first = true;
@ -91,10 +91,10 @@ export const generateTableList = async (
adv: { adv: {
js: `\ js: `\
<div {...props} className={cx(props.className, "")}> <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>`, </div>`,
jsBuilt: `\ jsBuilt: `\
render(React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }),React.createElement(FormatValue, { value: col.value, name: col.name, gen_fields: gen_fields, ${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, props,
}, },
}; };
generateForm(async (props: any) => {}, props, tablelist, false); generateForm(async (props: any) => {}, props, tablelist, false, true);
tab_detail?.edit.setProp("breadcrumb", { tab_detail?.edit.setProp("breadcrumb", {
mode: "raw", mode: "raw",
value: `\ 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([ tab_detail?.edit.setChilds([
{ {
type: "item", type: "item",

View File

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

View File

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

View File

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

View File

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

View File

@ -10,14 +10,14 @@ export const MDRenderMaster: FC<{
min_size: any; min_size: any;
child: any; child: any;
on_init: () => MDLocal; on_init: () => MDLocal;
breadcrumb: () => Array<any> breadcrumb: () => Array<any>;
}> = ({ child, on_init, min_size, size, breadcrumb }) => { }> = ({ child, on_init, min_size, size, breadcrumb }) => {
useEffect(() => { useEffect(() => {
console.log("master");
let md = on_init(); let md = on_init();
md.breadcrumb.list = breadcrumb(); md.header.breadcrumb = breadcrumb();
if(!isEditor){ md.header.render();
md.breadcrumb.reload();
}
if (md) { if (md) {
let width = 0; let width = 0;
let min_width = 0; let min_width = 0;
@ -33,8 +33,8 @@ export const MDRenderMaster: FC<{
md.panel.min_size = min_width; md.panel.min_size = min_width;
md.panel.size = width; md.panel.size = width;
} }
}; }
}, []) }, []);
return <>{child}</>; 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.gen_table = gen_table;
md.props.on_init = on_init; md.props.on_init = on_init;
if (!mdr.master || (mdr.master && !get(mdr, "master.edit.childs.0.childs.length"))) { if (!mdr.master || (mdr.master && !get(mdr, "master.edit.childs.0.childs.length"))) {
md.breadcrumb.list = [ md.header.breadcrumb = [
{ {
label: ( label: (
<> <>
@ -39,7 +39,7 @@ export const editorMDInit = (md: MDLocal, mdr: MDRef, arg: MDProps) => {
]; ];
md.status = "unready"; md.status = "unready";
} else { } else {
md.breadcrumb.list = []; md.header.breadcrumb = [];
md.status = "ready"; 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 = { export type MDLocalInternal = {
name: string; name: string;
status: "init" | "unready" | "ready"; status: "init" | "unready" | "ready";
breadcrumb: {list:BreadItem[], render: () => void, reload: () => void}; header: { breadcrumb: BreadItem[]; render: () => void };
actions: MDActions; actions: MDActions;
selected: any; selected: any;
tab: { tab: {
@ -89,11 +89,13 @@ export type MDLocal = MDLocalInternal & { render: (force?: boolean) => void };
export const MasterDetailType = `const md = { export const MasterDetailType = `const md = {
name: string; name: string;
status: string; status: string;
breadcrumb: { header: {
label: React.ReactNode; breadcrumb: {
url?: string; label: React.ReactNode;
onClick?: () => void; url?: string;
}[]; onClick?: () => void;
}[]
};
actions: ( actions: (
{ {
action?: string; action?: string;

View File

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

View File

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

View File

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

View File

@ -74,6 +74,7 @@ export { prasi_gen } from "@/gen/prasi_gen";
export { password } from "@/utils/password"; export { password } from "@/utils/password";
export { generateTableList } from "@/comps/md/gen/gen-table-list"; export { generateTableList } from "@/comps/md/gen/gen-table-list";
export { generateForm } from "@/comps/form/gen/gen-form"; export { generateForm } from "@/comps/form/gen/gen-form";
export { generateSelect } from "@/comps/md/gen/md-select";
/** Session */ /** Session */
export { export {
@ -81,11 +82,9 @@ export {
RG, RG,
UserSession, UserSession,
} from "@/preset/login/utils/register"; } from "@/preset/login/utils/register";
export { prasi_user } from "@/preset/login/utils/user";
export { Login } from "@/preset/login/Login"; export { Login } from "@/preset/login/Login";
export { logout } from "@/preset/login/utils/logout"; export { logout } from "@/preset/login/utils/logout";
export { generateLogin } from "@/preset/login/utils/generate"; export { generateLogin } from "@/preset/login/utils/generate";
export { select as generateSelect } from "@/preset/login/utils/select";
export { Card } from "@/comps/custom/Card"; export { Card } from "@/comps/custom/Card";
@ -114,4 +113,4 @@ export { getPathname } from "./utils/pathname";
export * from "@/comps/ui/typeahead"; export * from "@/comps/ui/typeahead";
export * from "@/comps/ui/input"; 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 `\ return `\
async ({ form, error }: IForm) => { async ({ form, error }: IForm) => {
if (isEditor) return false;
if (typeof form !== "object") return false; if (typeof form !== "object") return false;
if (typeof error === "object" && Object.keys(error).length > 0) 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 { FC, useEffect } from "react";
import { prasi_user } from "./utils/user"; import { loadSession } from "./utils/load";
const w = window as unknown as { const w = window as unknown as {
user: any;
prasi_home: Record<string, string>; prasi_home: Record<string, string>;
}; };
@ -15,8 +16,11 @@ export const Login: FC<LGProps> = (props) => {
w.prasi_home = props.url_home[0]; w.prasi_home = props.url_home[0];
useEffect(() => { useEffect(() => {
try { try {
const home = prasi_user.prasi_home[prasi_user.user.m_role.name]; loadSession();
navigate(home); if (w.user) {
const home = w.prasi_home[w.user.m_role.name];
navigate(home);
}
} catch (e: any) {} } catch (e: any) {}
}, []); }, []);
return <>{props.body}</>; return <>{props.body}</>;

View File

@ -54,7 +54,8 @@ export const generateLogin = async (
}); });
if (data_user) { if (data_user) {
registerSession({ data: data_user, expired: null }); 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); navigate(home);
} }
}else{ }else{

View File

@ -4,27 +4,26 @@ import { logout } from "./logout";
const w = window as any; const w = window as any;
const parse = parser.exportAsFunctionAny("en-US"); const parse = parser.exportAsFunctionAny("en-US");
export const loadSession = (url_login?: string) => { export const loadSession = (url_login?: string) => {
try { if (!isEditor) {
const user = localStorage.getItem("user"); try {
if (user) { const user = localStorage.getItem("user");
const raw = JSON.parse(user); if (user) {
w.user = raw.data; const raw = JSON.parse(user);
if (typeof raw === "object") { w.user = raw.data;
const session: UserSession = raw; if (typeof raw === "object") {
const expired = parse(session.expired); const session: UserSession = raw;
if ( const expired = parse(session.expired);
typeof expired === "object" && if (expired instanceof Date) {
expired instanceof Date if (new Date() > expired) {
) { if (url_login) logout(url_login);
if (new Date() > expired) { }
logout(url_login);
} }
} }
} else {
if (url_login) logout(url_login);
} }
} else { } catch (e) {
logout(url_login); 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")) { if (localStorage.getItem("user")) {
localStorage.removeItem("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; desktop?: ReactNode;
tablet?: ReactNode; tablet?: ReactNode;
child?: ReactNode; child?: ReactNode;
children: ReactNode; default_layout: ReactNode;
exception?: Array<string>; exception?: Array<string>;
defaultLayout: ReactNode; blank_layout: ReactNode;
}; };
export const Layout: FC<LYTChild> = (props) => { export const Layout: FC<LYTChild> = (props) => {
@ -64,12 +64,18 @@ export const Layout: FC<LYTChild> = (props) => {
const no_layout = props.exception; const no_layout = props.exception;
useEffect(() => { useEffect(() => {
loadSession("/auth/login"); loadSession("/auth/login");
render();
}, []); }, []);
if (Array.isArray(no_layout))
if (!isEditor && Array.isArray(no_layout)) {
if (no_layout.length) { if (no_layout.length) {
if (no_layout.includes(path)) { 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 = ( export const softDeleteFilter = (
where: any, where: any,
soft: { soft: {
field: string; field: string;
type: "boolean" | "nullable"; 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 : {}; const defaultParam = typeof where === "object" ? where : {};
if (isEmptyString(soft.field) || isEmptyString(soft.type))
return defaultParam;
const result = { const result = {
AND: [ AND: [
typeof where === "object" typeof defaultParam === "object" ? { ...defaultParam } : {},
? { ...defaultParam } {
: { [soft.field]:
[soft.field]: soft.type === "boolean"
soft.type === "boolean" ? false
? true :null,
: { },
not: null,
},
},
], ],
}; };
console.log(result);
return result; return result;
}; };