This commit is contained in:
rizky 2024-03-25 19:49:50 -07:00
parent 99e6c1c530
commit 0a02fc3c0e
25 changed files with 4361 additions and 83 deletions

View File

@ -40,6 +40,11 @@ export const Relation: FC<RelationProps> = ({
if (form) { if (form) {
local.status = "loading"; local.status = "loading";
local.render(); local.render();
if (form.cache[name]) {
local.pk_field = form.cache[name].pk_field;
local.list = form.cache[name].list;
} else {
const table_fn = (db as any)[relation.table]; const table_fn = (db as any)[relation.table];
const select = {} as any; const select = {} as any;
local.pk_field = ""; local.pk_field = "";
@ -67,6 +72,8 @@ export const Relation: FC<RelationProps> = ({
return { value: item[local.pk_field], label: label.join(" - ") }; return { value: item[local.pk_field], label: label.join(" - ") };
}); });
} }
form.cache[name] = { list: local.list, pk_field: local.pk_field };
}
const found = local.list.find((e) => { const found = local.list.find((e) => {
if (typeof value === "object") { if (typeof value === "object") {
@ -109,9 +116,10 @@ export const Relation: FC<RelationProps> = ({
content={ content={
<div <div
className={cx( className={cx(
"c-text-sm", "c-text-sm c-relative c-overflow-auto",
css` css`
width: ${local.ref.input?.clientWidth || 100}px; width: ${local.ref.input?.clientWidth || 100}px;
max-height: 300px;
` `
)} )}
> >
@ -124,15 +132,25 @@ export const Relation: FC<RelationProps> = ({
</> </>
)} )}
{local.status === "ready" && ( {local.status === "ready" && (
<> <div className="c-flex c-flex-1 c-flex-col">
{filtered.map((item, idx) => { {filtered.map((item, idx) => {
let is_active = false;
if (typeof value === "object") {
const c = (value as any).connect;
if (c) {
is_active = item.value === c[local.pk_field];
}
} else {
is_active = item.value === value;
}
return ( return (
<div <div
tabIndex={0} tabIndex={0}
key={item.value + "_" + idx} key={item.value + "_" + idx}
className={cx( className={cx(
"c-px-3 c-py-1 cursor-pointer option-item", "c-px-3 c-py-1 cursor-pointer option-item",
item.value === value is_active
? "c-bg-blue-600 c-text-white" ? "c-bg-blue-600 c-text-white"
: "hover:c-bg-blue-50", : "hover:c-bg-blue-50",
idx > 0 && "c-border-t", idx > 0 && "c-border-t",
@ -149,11 +167,11 @@ export const Relation: FC<RelationProps> = ({
} }
}} }}
> >
{item.label} {item.label || "-"}
</div> </div>
); );
})} })}
</> </div>
)} )}
</div> </div>
} }
@ -197,7 +215,7 @@ export const Relation: FC<RelationProps> = ({
/> />
{!local.open && ( {!local.open && (
<div className="c-absolute c-text-sm c-inset-0 c-px-3 c-flex c-items-center"> <div className="c-absolute c-text-sm c-inset-0 c-px-3 c-flex c-items-center">
{local.label} {local.label || "-"}
</div> </div>
)} )}
</div> </div>

View File

@ -1,16 +1,22 @@
import { Form as FForm } from "@/comps/ui/form"; import { Form as FForm } from "@/comps/ui/form";
import { Toaster } from "@/comps/ui/sonner";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { FC } from "react"; import { FC } from "react";
import { createPortal } from "react-dom";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { FormHook } from "./utils/utils"; import { FormHook } from "./utils/utils";
import { AlertTriangle, Check, Loader2 } from "lucide-react";
import { cn } from "@/utils";
export const Form: FC<{ export const Form: FC<{
on_init: (arg: { submit: any }) => any; on_init: (arg: { submit: any }) => any;
on_load: () => any; on_load: () => any;
on_submit: (arg: { form: any; error: any }) => any; on_submit: (arg: { form: any; error: any }) => Promise<any>;
body: any; body: any;
form: FormHook; form: FormHook;
PassProp: any; PassProp: any;
cache: () => any;
layout: "auto" | "1-col" | "2-col"; layout: "auto" | "1-col" | "2-col";
}> = ({ }> = ({
on_init, on_init,
@ -19,10 +25,11 @@ export const Form: FC<{
form, form,
PassProp, PassProp,
on_submit, on_submit,
cache,
layout: _layout, layout: _layout,
}) => { }) => {
const form_hook = useForm<any>({ const form_hook = useForm<any>({
defaultValues: on_load, defaultValues: {},
}); });
const local = useLocal({ const local = useLocal({
@ -33,6 +40,13 @@ export const Form: FC<{
}); });
form.hook = form_hook; form.hook = form_hook;
if (!form.cache && typeof cache === "function") {
try {
form.cache = cache() || {};
} catch (e) {}
}
if (!form.cache) form.cache = {};
if (!form.validation) { if (!form.validation) {
form.validation = {}; form.validation = {};
} }
@ -45,7 +59,19 @@ export const Form: FC<{
const submit = () => { const submit = () => {
clearTimeout(local.submit_timeout); clearTimeout(local.submit_timeout);
local.submit_timeout = setTimeout(() => { local.submit_timeout = setTimeout(async () => {
toast.loading(
<>
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
Saving ...
</>,
{
dismissible: true,
className: css`
background: #e4f7ff;
`,
}
);
const data = form.hook.getValues(); const data = form.hook.getValues();
form.hook.clearErrors(); form.hook.clearErrors();
for (const [k, v] of Object.entries(form.validation)) { for (const [k, v] of Object.entries(form.validation)) {
@ -61,22 +87,88 @@ export const Form: FC<{
} }
} }
on_submit({ await on_submit({
form: data, form: data,
error: form.hook.formState.errors, error: form.hook.formState.errors,
}); });
toast.dismiss();
if (Object.keys(form.hook.formState.errors).length > 0) {
toast.error(
<div className="c-flex c-text-red-600 c-items-center">
<AlertTriangle className="c-h-4 c-w-4 c-mr-1" />
Save Failed, please correct{" "}
{Object.keys(form.hook.formState.errors).length} errors.
</div>,
{
dismissible: true,
className: css`
background: #ffecec;
border: 2px solid red;
`,
}
);
} else {
toast.success(
<div className="c-flex c-text-blue-700 c-items-center">
<Check className="c-h-4 c-w-4 c-mr-1 " />
Data saved
</div>,
{
className: css`
background: #e4f5ff;
border: 2px solid blue;
`,
}
);
}
}, 300); }, 300);
}; };
if (!local.init) { if (!local.init) {
local.init = true; local.init = true;
on_init({ submit }); on_init({ submit });
const res = on_load();
const loaded = (values: any) => {
setTimeout(() => {
toast.dismiss();
});
if (!!values) {
for (const [k, v] of Object.entries(values)) {
form.hook.setValue(k, v);
}
}
local.render();
};
if (res instanceof Promise) {
setTimeout(() => {
if (!isEditor) {
toast.loading(
<>
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
Loading data...
</>
);
}
res.then(loaded);
});
} else {
loaded(res);
}
} }
form.submit = submit; form.submit = submit;
if (document.getElementsByClassName("prasi-toaster").length === 0) {
const elemDiv = document.createElement("div");
elemDiv.className = "prasi-toaster";
document.body.appendChild(elemDiv);
}
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
return ( return (
<FForm {...form_hook}> <FForm {...form_hook}>
{toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
<form <form
className={ className={
"flex-1 flex flex-col w-full items-stretch relative overflow-auto" "flex-1 flex flex-col w-full items-stretch relative overflow-auto"
@ -85,7 +177,6 @@ export const Form: FC<{
if (el) form.ref = el; if (el) form.ref = el;
}} }}
onSubmit={(e) => { onSubmit={(e) => {
console.log("on submit");
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
submit(); submit();

View File

@ -24,6 +24,7 @@ export type FormHook = {
ref: HTMLFormElement; ref: HTMLFormElement;
submit: any; submit: any;
label: Record<string, string>; label: Record<string, string>;
cache: any;
validation: Record<string, "required">; validation: Record<string, "required">;
render: () => void; render: () => void;
}; };

View File

@ -1,15 +1,26 @@
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { FC, useEffect } from "react"; import { FC, useEffect } from "react";
import DataGrid, { ColumnOrColumnGroup, SortColumn } from "react-data-grid"; import DataGrid, {
ColumnOrColumnGroup,
Row,
SortColumn,
} from "react-data-grid";
import "react-data-grid/lib/styles.css"; import "react-data-grid/lib/styles.css";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
type OnRowClick = {
row: any;
rows: any[];
idx: any;
event: React.MouseEvent<HTMLDivElement, MouseEvent>;
};
export const Table: FC<{ export const Table: FC<{
columns: () => Promise<ColumnOrColumnGroup<any>[]>; columns: () => Promise<ColumnOrColumnGroup<any>[]>;
on_load: () => Promise<any[]>; on_load: () => Promise<any[]>;
child: any; child: any;
PassProp: any; PassProp: any;
}> = ({ columns, on_load, child, PassProp }) => { row_click: (arg: OnRowClick) => void;
}> = ({ columns, on_load, child, PassProp, row_click }) => {
const local = useLocal({ const local = useLocal({
loading: false, loading: false,
data: undefined as unknown as any[], data: undefined as unknown as any[],
@ -56,6 +67,7 @@ export const Table: FC<{
return ( return (
<TableInternal <TableInternal
row_click={row_click}
columns={local.columns} columns={local.columns}
data={local.loading ? undefined : local.data} data={local.loading ? undefined : local.data}
render={local.render} render={local.render}
@ -67,7 +79,8 @@ const TableInternal: FC<{
columns: ColumnOrColumnGroup<any>[]; columns: ColumnOrColumnGroup<any>[];
data?: any[]; data?: any[];
render: () => void; render: () => void;
}> = ({ columns, data, render }) => { row_click: (arg: OnRowClick) => void;
}> = ({ columns, data, render, row_click }) => {
const local = useLocal({ const local = useLocal({
width: 0, width: 0,
height: 0, height: 0,
@ -134,7 +147,6 @@ const TableInternal: FC<{
> >
<DataGrid <DataGrid
columns={columns} columns={columns}
selectedRows={null}
sortColumns={sort} sortColumns={sort}
onSortColumnsChange={([col]) => { onSortColumnsChange={([col]) => {
local.sort = []; local.sort = [];
@ -160,6 +172,24 @@ const TableInternal: FC<{
typeof data === "undefined" typeof data === "undefined"
? undefined ? undefined
: { : {
renderRow(key, props) {
return (
<Row
key={key}
{...props}
onClick={(ev) => {
if (typeof row_click === "function") {
row_click({
event: ev,
idx: props.rowIdx,
row: props.row,
rows: data,
});
}
}}
/>
);
},
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">
<div className="c-max-w-[15%] c-flex c-flex-col c-items-center"> <div className="c-max-w-[15%] c-flex c-flex-col c-items-center">

51
comps/mst/MDAction.tsx Executable file
View File

@ -0,0 +1,51 @@
import { FC } from "react";
import { MasterDetailConfig } from "./type";
import { Button } from "../ui/button";
export const MDAction: FC<{ md: MasterDetailConfig }> = ({ md }) => {
return (
<div
className={cx(
"md-action c-flex c-space-x-2",
css`
button {
height: 25px;
}
`
)}
>
{md.ui.actions.length > 0 &&
md.ui.actions.map((e, idx) => {
return (
<Button
size="sm"
variant={e.type || "default"}
onClick={e.onClick}
className={cx(
"c-flex c-items-center",
css`
padding: 0px 8px !important;
`
)}
>
{e.icon && (
<span
className={cx(
"c-mr-[5px]",
css`
svg {
width: 15px;
height: 15px;
}
`
)}
dangerouslySetInnerHTML={{ __html: e.icon }}
></span>
)}{" "}
{e.label}
</Button>
);
})}
</div>
);
};

44
comps/mst/MDTitle.tsx Executable file
View File

@ -0,0 +1,44 @@
import { FC } from "react";
import { MasterDetailConfig } from "./type";
export const MDTitle: FC<{ md: MasterDetailConfig }> = ({ md }) => {
return (
<div>
{md.ui.breadcrumb.length > 0
? md.ui.breadcrumb.map((e, idx) => {
let label = (typeof e === "object" ? e?.[0] : e) || "-";
let url = typeof e === "object" ? e?.[1] : null;
return (
<>
{idx > 0 && (
<span className="c-pl-[6px] c-pr-[5px] c-text-gray-300">
/
</span>
)}
<span
onClick={() => {
if (url === "") {
md.ui.actions = [...md.ui.default_actions];
md.ui.breadcrumb = [];
md.ui.back = false;
md.selected = null;
md.render();
}
}}
className={cx(
typeof url === "string"
? "c-cursor-pointer hover:c-underline"
: "c-text-gray-500"
)}
>
{label}
</span>
</>
);
})
: md.ui.title}
</div>
);
};

163
comps/mst/MasterDetail.tsx Executable file
View File

@ -0,0 +1,163 @@
import { useLocal } from "@/utils/use-local";
import get from "lodash.get";
import { FC, useEffect } from "react";
import {
MasterDetailConfig,
MasterDetailLocal,
MasterDetailProp,
} from "./type";
import { Tab } from "../custom/Tab";
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
export const MasterDetail: FC<MasterDetailProp> = (props) => {
const { header, PassProp, master, detail, mode, title, actions } = props;
const md = useLocal<MasterDetailLocal & { cache_internal: any }>({
mode,
selected: null,
active_tab: "",
ui: {
back: false,
title: title,
breadcrumb: [],
default_actions: null as any,
actions: null as any,
},
cache_internal: {},
cache: null as any,
});
if (!md.ui.actions) {
md.ui.actions = actions(md);
md.ui.default_actions = actions(md);
}
if (!md.cache) {
md.cache = (name: string, opt?: { reset: boolean }) => {
if (!md.cache_internal[name] || opt?.reset) md.cache_internal[name] = {};
return md.cache_internal[name];
};
}
if (isEditor) {
useEffect(() => {
md.ui.title = title;
md.render();
}, [title]);
}
useEffect(() => {
let back = false;
if (md.selected && md.mode === "breadcrumb") {
back = true;
}
if (back !== md.ui.back) {
md.ui.back = back;
md.render();
}
}, [md.selected]);
return (
<div
className={cx(
"c-flex-1 c-flex-col c-flex c-w-full c-h-full c-overflow-hidden"
)}
>
<props.PassProp md={md}>{header}</props.PassProp>
<BreadcrumbMode props={props} md={md} />
</div>
);
};
const BreadcrumbMode: FC<{
props: MasterDetailProp;
md: MasterDetailConfig;
}> = ({ props, md }) => {
return (
<div className={cx("c-flex-1 c-flex-col c-flex")}>
<div
className={cx(md.selected && "c-hidden", "c-flex c-flex-1 c-flex-col")}
>
<props.PassProp md={md}>{props.master}</props.PassProp>
</div>
{md.selected && <Detail props={props} md={md} />}
</div>
);
};
const Detail: FC<{
props: MasterDetailProp;
md: MasterDetailConfig;
}> = ({ props, md }) => {
const childs = get(
props.detail,
"props.meta.item.component.props.detail.content.childs"
);
let idx = childs.findIndex(
(e: any) => e.name.toLowerCase() === md.active_tab.toLowerCase()
);
if (idx < 0) {
idx = 0;
md.active_tab = childs[idx].name;
setTimeout(md.render);
}
const content = childs[idx];
return (
<>
{childs.length > 1 && (
<Tabs
value={content.name}
className={cx(
css`
padding: 0;
button {
border-radius: 0;
}
`
)}
>
<TabsList
overrideClassName
className={cx(
"c-flex c-w-full c-rounded-none c-border-b c-border-gray-300 c-justify-start",
css`
padding: 0 !important;
height: auto !important;
`
)}
>
{childs.map((e: { name: string }) => {
return (
<TabsTrigger
value={e.name}
onClick={() => {
md.active_tab = e.name;
md.render();
}}
overrideClassName
>
<div
className={cx(
"c-p-1 c-h-10 c-flex c-items-center",
md.active_tab === e.name
? css`
border-bottom: 2px solid #3c82f6;
`
: "border-b-transparent"
)}
>
<div className={cx("c-mr-1 c-flex-1 c-px-1 c-flex")}>
{e.name}
</div>
</div>
</TabsTrigger>
);
})}
</TabsList>
</Tabs>
)}
<props.PassProp md={md}>{content}</props.PassProp>
</>
);
};

59
comps/mst/type.ts Executable file
View File

@ -0,0 +1,59 @@
import { ReactElement, ReactNode } from "react";
export type MasterDetailProp = {
header: any;
PassProp: any;
master: any;
detail: any;
mode: "breadcrumb" | "vertical" | "horizontal";
title: string;
actions: (md: any) => MasterDetailAction[];
};
type MasterDetailAction = {
label: string;
onClick?: () => Promise<void>;
type?: "default" | "ghost" | "secondary" | "destructive";
icon?: string;
};
export type MasterDetailLocal = {
mode: MasterDetailProp["mode"];
selected: null | Record<string, any>;
active_tab: string;
ui: {
back: boolean;
title: string;
breadcrumb: ([string, string] | string)[];
default_actions: MasterDetailAction[];
actions: MasterDetailAction[];
};
cache: (name: string, opt?: { reset: boolean }) => any;
};
export type MasterDetailConfig = MasterDetailLocal & { render: () => void };
const action_type = `{
label: string;
onClick?: () => Promise<void>;
type?: "default" | "ghost" | "secondary" | "destructive";
icon?: string;
}`;
export const master_detail_typings = {
md: `{
mode: "breadcrumb" | "vertical" | "horizontal";
selected: null | Record<string, any>;
active_tab: string;
render: () => void;
ui: {
back: boolean;
title: string;
breadcrumb: ([string, string] | string)[];
default_actions: ${action_type}[];
actions: ${action_type}[];
};
cache: (name: string, opt?: { reset: boolean }) => any;
}`,
action_type,
};

29
comps/ui/sonner.tsx Executable file
View File

@ -0,0 +1,29 @@
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="c-toaster c-group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
)
}
export { Toaster }

View File

@ -1,39 +1,47 @@
import * as React from "react" import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs" import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/utils" import { cn } from "@/utils";
const Tabs = TabsPrimitive.Root const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef< const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>, React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {
overrideClassName?: boolean;
}
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<TabsPrimitive.List <TabsPrimitive.List
ref={ref} ref={ref}
className={cn( className={cn(
"c-inline-flex c-h-10 c-items-center c-justify-center c-rounded-md c-bg-muted c-p-1 c-text-muted-foreground", props.overrideClassName === true
? ""
: "c-inline-flex c-h-10 c-items-center c-justify-center c-rounded-md c-bg-muted c-p-1 c-text-muted-foreground",
className className
)} )}
{...props} {...props}
/> />
)) ));
TabsList.displayName = TabsPrimitive.List.displayName TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef< const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>, React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & {
overrideClassName?: boolean;
}
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"c-inline-flex c-items-center c-justify-center c-whitespace-nowrap c-rounded-sm c-px-3 c-py-1.5 c-text-sm c-font-medium c-ring-offset-background c-transition-all focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2 disabled:c-pointer-events-none disabled:c-opacity-50 data-[state=active]:c-bg-background data-[state=active]:c-text-foreground data-[state=active]:c-shadow-sm", props.overrideClassName === true
? ""
: "c-inline-flex c-items-center c-justify-center c-whitespace-nowrap c-rounded-sm c-px-3 c-py-1.5 c-text-sm c-font-medium c-ring-offset-background c-transition-all focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2 disabled:c-pointer-events-none disabled:c-opacity-50 data-[state=active]:c-bg-background data-[state=active]:c-text-foreground data-[state=active]:c-shadow-sm",
className className
)} )}
{...props} {...props}
/> />
)) ));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef< const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>, React.ElementRef<typeof TabsPrimitive.Content>,
@ -47,7 +55,7 @@ const TabsContent = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
TabsContent.displayName = TabsPrimitive.Content.displayName TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent } export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@ -1,9 +1,8 @@
import get from "lodash.get"; import get from "lodash.get";
import { on_load } from "./gen_form/on_load"; import { GFCol as Col, formatName } from "../utils";
import { on_submit } from "./gen_form/on_submit"; import { NewFieldArg, newField } from "./new_field";
import { GFCol as Col } from "./gen_form/type"; import { on_load } from "./on_load";
import { NewFieldArg, newField } from "./gen_form/new_field"; import { on_submit } from "./on_submit";
import capitalize from "lodash.capitalize";
export const gen_form = (modify: (data: any) => void, data: any) => { export const gen_form = (modify: (data: any) => void, data: any) => {
const mode = JSON.parse(data.gen_mode.value) as ( const mode = JSON.parse(data.gen_mode.value) as (
@ -59,7 +58,6 @@ export const gen_form = (modify: (data: any) => void, data: any) => {
select[col.name] = true; select[col.name] = true;
} }
} }
console.log(new_fields);
const result: any = {}; const result: any = {};
if (pk) { if (pk) {
@ -76,7 +74,6 @@ export const gen_form = (modify: (data: any) => void, data: any) => {
result["body"] = data["body"]; result["body"] = data["body"];
const childs = get(result, "body.content.childs"); const childs = get(result, "body.content.childs");
if (Array.isArray(childs)) { if (Array.isArray(childs)) {
result.body.content.childs = new_fields.map(newField); result.body.content.childs = new_fields.map(newField);
} }
} }
@ -84,11 +81,3 @@ export const gen_form = (modify: (data: any) => void, data: any) => {
alert("Prop Generated!"); alert("Prop Generated!");
}; };
const formatName = (name: string) => {
return name
.split("_")
.filter((e) => e.length > 1)
.map((e) => capitalize(e))
.join(" ");
};

View File

@ -265,10 +265,8 @@ export const newField = (arg: NewFieldArg) => {
idx: 11, idx: 11,
name: "prop_11", name: "prop_11",
type: "string", type: "string",
value: value: "async () => {\n return {\n };\n}",
'async () => {\n return {\n orderBy: {\n regional: "asc",\n },\n };\n}', valueBuilt: " async () => {\n return {\n };\n};\n",
valueBuilt:
' async () => {\n return {\n orderBy: {\n regional: "asc"\n }\n };\n};\n',
meta: { meta: {
type: "text", type: "text",
}, },

View File

@ -1,21 +1,26 @@
import { GFCol } from "./type"; import { GFCol } from "../utils";
export const on_load = ({ export const on_load = ({
pk, pk,
table, table,
select, select,
pks, pks,
opt,
}: { }: {
pk: GFCol; pk: GFCol;
table: string; table: string;
select: any; select: any;
pks: Record<string, string>; pks: Record<string, string>;
opt?: {
before_load: string;
};
}) => { }) => {
return `\ return `\
async (opt) => { async (opt) => {
if (isEditor) return {}; if (isEditor) return {};
let id = ${pk.type === "int" ? "parseInt(params.id)" : "params.id"}; let id = ${pk.type === "int" ? "parseInt(params.id)" : "params.id"};
${opt?.before_load}
if (id){ if (id){
const item = await db.${table}.findFirst({ const item = await db.${table}.findFirst({
@ -30,7 +35,8 @@ async (opt) => {
.map(([k, v]) => { .map(([k, v]) => {
return `\ return `\
if (k === "${k}") { if (k === "${k}") {
item[k] = { connect: { ${v}: v["${v}"] } } as any; if (v?.["${v}"]) item[k] = { connect: { ${v}: v?.["${v}"] } } as any;
else delete item[k];
}`; }`;
}) })
.join("\n")} .join("\n")}

View File

@ -1,4 +1,4 @@
import { GFCol } from "./type"; import { GFCol } from "../utils";
export const on_submit = ({ export const on_submit = ({
pk, pk,

View File

@ -1,6 +0,0 @@
export type GFCol = {
name: string;
type: string;
is_pk: boolean;
optional: boolean;
};

46
gen/gen_md/form_before_load.ts Executable file
View File

@ -0,0 +1,46 @@
export const form_before_load = (pk: string, title: string, name: string) => {
return `
const after_load = (item: any) => {
const set_actions = () =>
(md.ui.actions = [
{
label: "Delete",
type: "destructive",
onClick: async () => {
if (confirm("Are you sure ?")) {
md.ui.actions = [{ label: "Deleting...", type: "ghost" }];
md.render();
await db.m_aset.delete({ where: { ${pk}: item.${pk} } });
setTimeout(() => {
md.ui.actions = [...md.ui.default_actions];
md.ui.breadcrumb = [];
md.ui.back = false;
md.selected = null;
md.render();
});
}
},
icon: \`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>\`,
},
{
label: "Save",
onClick: async () => {
md.ui.actions = [{ label: "Saving...", type: "ghost" }];
md.render();
await md.cache("form")._submit();
setTimeout(() => {
set_actions();
md.render();
}, 500);
},
icon: \`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>\`,
},
]);
set_actions();
md.ui.breadcrumb = [[${title}, ""], item?.nama_aset_komersial];
md.render();
};
`;
};

2887
gen/gen_md/gen_detail.ts Executable file

File diff suppressed because it is too large Load Diff

223
gen/gen_md/gen_header.ts Executable file
View File

@ -0,0 +1,223 @@
import { createId } from "@paralleldrive/cuid2";
export const gen_header = () => {
return {
id: createId(),
name: "prop_1",
type: "item",
dim: {
w: "full",
h: 40,
hUnit: "px",
},
childs: [
{
id: createId(),
name: "breadcrumb",
type: "item",
dim: {
w: "full",
h: 40,
hUnit: "px",
},
childs: [
{
id: createId(),
name: "back",
type: "item",
dim: {
w: "fit",
h: "full",
},
childs: [
{
id: createId(),
name: "icon",
type: "item",
dim: {
w: "full",
h: "full",
},
childs: [],
adv: {
html: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"\n stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left">\n <path d="m15 18-6-6 6-6" />\n</svg>',
css: "",
},
layout: {
dir: "col",
align: "center",
gap: 0,
wrap: "flex-nowrap",
},
},
{
id: createId(),
name: "back_text",
type: "text",
dim: {
w: "fit",
h: "full",
},
layout: {
align: "center",
dir: "col",
gap: 0,
},
text: "",
html: "<div>Back</div>",
adv: {
css: "",
},
},
],
adv: {
js: '<>\n {md.ui.back && (\n <div\n {...props}\n className={cx(props.className, "")}\n onClick={() => {\n md.ui.actions = [...md.ui.default_actions];\n md.ui.breadcrumb = [];\n md.ui.back = false;\n md.selected = null;\n md.render();\n }}\n >\n {children}\n </div>\n )}\n</>',
jsBuilt:
'render(/* @__PURE__ */ React.createElement(React.Fragment, null, md.ui.back && /* @__PURE__ */ React.createElement(\n "div",\n {\n ...props,\n className: cx(props.className, ""),\n onClick: () => {\n md.ui.actions = [...md.ui.default_actions];\n md.ui.breadcrumb = [];\n md.ui.back = false;\n md.selected = null;\n md.render();\n }\n },\n children\n)));\n',
css: "& {\n display: flex;\n cursor: pointer;\n\n // &.mobile {}\n // &.desktop {}\n &:hover {\n background: rgb(237, 246, 255);\n }\n}",
},
script: {},
layout: {
dir: "row",
align: "top-left",
gap: 0,
wrap: "flex-nowrap",
},
border: {
style: "solid",
stroke: {
r: 1,
},
color: "#ececeb",
},
padding: {
l: 10,
b: 0,
t: 0,
r: 10,
},
},
{
id: createId(),
name: "title",
type: "item",
childs: [],
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n <MDTitle md={md} />\n</div>',
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, /* @__PURE__ */ React.createElement(MDTitle, { md })));\n',
},
script: {},
padding: {
l: 10,
b: 0,
t: 0,
r: 10,
},
dim: {
w: "full",
h: "full",
wUnit: "px",
hUnit: "px",
},
layout: {
dir: "col",
align: "left",
gap: 0,
wrap: "flex-nowrap",
},
},
{
id: createId(),
name: "right",
type: "item",
childs: [
{
id: createId(),
name: "mode",
type: "item",
dim: {
w: "fit",
h: "full",
},
childs: [],
adv: {
js: '<>\n {!md.ui.back && (\n <div {...props} className={cx(props.className, "")}>\n {children}\n </div>\n )}\n</>',
jsBuilt:
'render(/* @__PURE__ */ React.createElement(React.Fragment, null, !md.ui.back && /* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, children)));\n',
css: "",
},
script: {},
padding: {
l: 0,
b: 0,
t: 0,
r: 10,
},
},
{
id: createId(),
name: "actions",
type: "item",
dim: {
w: "fit",
h: "full",
},
childs: [],
adv: {
js: '<div {...props} className={cx(props.className, "")}>\n <MDAction md={md} />\n</div>',
jsBuilt:
'render(/* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, /* @__PURE__ */ React.createElement(MDAction, { md })));\n',
css: "",
},
script: {},
padding: {
l: 0,
b: 0,
t: 0,
r: 10,
},
},
],
layout: {
dir: "row",
align: "top-left",
gap: 0,
wrap: "flex-nowrap",
},
},
],
adv: {
js: '<>\n {md.mode === "breadcrumb" && (\n <div {...props} className={cx(props.className, "")}>\n {children}\n </div>\n )}\n</>',
jsBuilt:
'render(/* @__PURE__ */ React.createElement(React.Fragment, null, md.mode === "breadcrumb" && /* @__PURE__ */ React.createElement("div", { ...props, className: cx(props.className, "") }, children)));\n',
css: "",
},
script: {},
padding: {
l: 0,
b: 0,
t: 0,
r: 0,
},
layout: {
dir: "row",
align: "left",
gap: 0,
wrap: "flex-nowrap",
},
border: {
style: "solid",
stroke: {
b: 1,
},
color: "#ccc",
},
},
],
adv: {
css: "",
},
hidden: false,
};
};

302
gen/gen_md/gen_master.ts Executable file
View File

@ -0,0 +1,302 @@
import { createId } from "@paralleldrive/cuid2";
export const gen_master = () => {
const res = {
id: createId(),
name: "prop_1",
type: "item",
dim: {
w: "full",
h: "full",
},
childs: [
{
id: createId(),
adv: {
js: "<Table on_load={on_load} columns={columns} child={child} PassProp={PassProp} />",
css: "",
jsBuilt:
"render(/* @__PURE__ */ React.createElement(Table, { on_load, columns, child, PassProp }));\n",
},
dim: {
h: "full",
w: "full",
},
name: "table",
type: "item",
childs: [
{
id: createId(),
adv: {
css: "",
},
dim: {
h: "full",
w: "full",
},
name: "jsx: child",
type: "item",
childs: [
{
id: createId(),
adv: {
js: '<>{cell.key !== "action" && cell.value}</>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement(React.Fragment, null, cell.key !== "action" && cell.value));\n',
},
dim: {
h: "full",
w: "full",
},
name: "text",
type: "item",
childs: [],
script: {},
},
{
id: createId(),
adv: {
js: '<>\n {cell.key === "action" && (\n <div className="flex items-center h-full">\n <div\n className={cx(\n "bg-white border flex items-center px-3 cursor-pointer capitalize",\n css`\n height:25px; \n &:hover {\n background:blue;\n color:white;\n }\n `,\n )}\n >\n {cell.value}\n </div>\n </div>\n )}\n</>',
css: "",
jsBuilt:
'render(/* @__PURE__ */ React.createElement(React.Fragment, null, cell.key === "action" && /* @__PURE__ */ React.createElement("div", { className: "flex items-center h-full" }, /* @__PURE__ */ React.createElement(\n "div",\n {\n className: cx(\n "bg-white border flex items-center px-3 cursor-pointer capitalize",\n css`\n height:25px; \n &:hover {\n background:blue;\n color:white;\n }\n `\n )\n },\n cell.value\n))));\n',
},
dim: {
h: "full",
w: "full",
},
name: "action",
type: "item",
childs: [],
script: {},
},
],
},
],
script: {},
component: {
id: "df4e3552-3221-496c-b07b-0c1295f811be",
props: {
child: {
idx: 3,
meta: {
type: "content-element",
},
name: "prop_3",
type: "string",
value: '"hello"',
content: {
id: createId(),
adv: {
css: "",
js: "<>{children}</>",
jsBuilt:
"render(/* @__PURE__ */ React.createElement(React.Fragment, null, children));\n",
},
dim: {
h: "full",
w: "full",
},
name: "prop_3",
type: "item",
childs: [
{
id: createId(),
adv: {
js: '<>\n {cell.key === "id" && (\n <div {...props} className={cx(props.className, "")}>\n {cell.value}\n </div>\n )}\n</>',
css: "",
jsBuilt:
'render(\n React.createElement(\n React.Fragment,\n null,\n cell.key === "id" &&\n React.createElement(\n "div",\n Object.assign({}, props, { className: cx(props.className, "") }),\n cell.value\n )\n )\n)',
},
dim: {
h: "full",
w: "full",
},
name: "id",
type: "item",
childs: [],
script: {},
},
{
id: createId(),
adv: {
js: '<>\n {cell.key === "nama_aset_komersial" && (\n <div {...props} className={cx(props.className, "")}>\n {cell.value}\n </div>\n )}\n</>',
css: "",
jsBuilt:
'render(\n React.createElement(\n React.Fragment,\n null,\n cell.key === "nama_aset_komersial" &&\n React.createElement(\n "div",\n Object.assign({}, props, { className: cx(props.className, "") }),\n cell.value\n )\n )\n)',
},
dim: {
h: "full",
w: "full",
},
name: "nama_aset_komersial",
type: "item",
childs: [],
script: {},
},
{
id: createId(),
adv: {
js: '<>\n {cell.key === "m_cabang" && (\n <div {...props} className={cx(props.className, "")}>\n {cell.value?.["nama_cabang"]}\n </div>\n )}\n</>',
css: "",
jsBuilt:
'render(\n React.createElement(\n React.Fragment,\n null,\n cell.key === "m_cabang" &&\n React.createElement(\n "div",\n Object.assign({}, props, { className: cx(props.className, "") }),\n cell.value?.["nama_cabang"]\n )\n )\n)',
},
dim: {
h: "full",
w: "full",
},
name: "m_cabang",
type: "item",
childs: [],
script: {},
},
{
id: createId(),
adv: {
js: '<>\n {cell.key === "m_regional" && (\n <div {...props} className={cx(props.className, "")}>\n {cell.value?.["regional"]}\n </div>\n )}\n</>',
css: "",
jsBuilt:
'render(\n React.createElement(\n React.Fragment,\n null,\n cell.key === "m_regional" &&\n React.createElement(\n "div",\n Object.assign({}, props, { className: cx(props.className, "") }),\n cell.value?.["regional"]\n )\n )\n)',
},
dim: {
h: "full",
w: "full",
},
name: "m_regional",
type: "item",
childs: [],
script: {},
},
{
id: createId(),
adv: {
js: '<>\n {cell.key === "luas_setifikat" && (\n <div {...props} className={cx(props.className, "")}>\n {cell.value}\n </div>\n )}\n</>',
css: "",
jsBuilt:
'render(\n React.createElement(\n React.Fragment,\n null,\n cell.key === "luas_setifikat" &&\n React.createElement(\n "div",\n Object.assign({}, props, { className: cx(props.className, "") }),\n cell.value\n )\n )\n)',
},
dim: {
h: "full",
w: "full",
},
name: "luas_setifikat",
type: "item",
childs: [],
script: {},
},
{
id: createId(),
adv: {
js: '<>\n {cell.key === "tanggal_sertifikat" && (\n <div {...props} className={cx(props.className, "")}>\n {cell.value}\n </div>\n )}\n</>',
css: "",
jsBuilt:
'render(\n React.createElement(\n React.Fragment,\n null,\n cell.key === "tanggal_sertifikat" &&\n React.createElement(\n "div",\n Object.assign({}, props, { className: cx(props.className, "") }),\n cell.value\n )\n )\n)',
},
dim: {
h: "full",
w: "full",
},
name: "tanggal_sertifikat",
type: "item",
childs: [],
script: {},
},
],
hidden: false,
},
typings:
"const typings = {\n cell: `{ key: string, value: any }`,\n row: `any`,\n idx: `number`,\n rows: `any[]`,\n}",
valueBuilt: ' "hello";\n',
},
columns: {
idx: 0,
meta: {
type: "text",
},
name: "prop_1",
type: "string",
value:
'async (): Promise<\n { key: string; name: string; width?: number; frozen?: boolean }[]\n > => {\n return [\n { key: "id", name: "#", width: 60, frozen: true },\n {"key":"nama_aset_komersial","name":"Nama Aset Komersial"},\n {"key":"m_cabang","name":"Cabang"},\n {"key":"m_regional","name":"Regional"},\n {"key":"luas_setifikat","name":"Luas Setifikat"},\n {"key":"tanggal_sertifikat","name":"Tanggal Sertifikat"}\n ];\n }',
valueBuilt:
' async () => {\n return [\n { key: "id", name: "#", width: 60, frozen: true },\n { "key": "nama_aset_komersial", "name": "Nama Aset Komersial" },\n { "key": "m_cabang", "name": "Cabang" },\n { "key": "m_regional", "name": "Regional" },\n { "key": "luas_setifikat", "name": "Luas Setifikat" },\n { "key": "tanggal_sertifikat", "name": "Tanggal Sertifikat" }\n ];\n};\n',
},
on_load: {
idx: 1,
meta: {
type: "text",
},
name: "prop_1",
type: "string",
value:
"async () => {\n if (isEditor)\n return [\n {\n id: createId(),\n },\n });\n\n return items;\n}",
valueBuilt:
' async () => {\n if (isEditor)\n return [\n {\n id: createId(),\n tanggal_sertifikat: "sample"\n }\n ];\n const items = await db.m_aset.findMany({\n select: {\n id: true,\n nama_aset_komersial: true,\n m_cabang: {\n select: {\n id: true,\n nama_cabang: true\n }\n },\n m_regional: {\n select: {\n regional: true,\n id: true\n }\n },\n luas_setifikat: true,\n tanggal_sertifikat: true\n },\n orderBy: {\n id: "desc"\n }\n });\n return items;\n};\n',
},
generate: {
idx: 5,
name: "prop_5",
type: "string",
value: '"n"',
valueBuilt: '"n"',
meta: {
type: "option",
},
},
gen_table: {
idx: 7,
name: "prop_7",
type: "string",
value: '""',
valueBuilt: '""',
meta: {
type: "option",
},
},
gen_fields: {
idx: 9,
name: "prop_9",
type: "string",
value: "[]",
valueBuilt: "[]",
meta: {
type: "option",
},
},
row_click: {
idx: 11,
name: "prop_11",
type: "string",
value:
"({ row, rows, idx, event }: OnRowClick) => {\n md.selected = row;\n md.render();\n};\n\ntype OnRowClick = {\n row: any;\n rows: any[];\n idx: any;\n event: React.MouseEvent<HTMLDivElement, MouseEvent>;\n}",
valueBuilt:
" ({ row, rows, idx, event }) => {\n md.selected = row;\n md.render();\n};\n",
meta: {
type: "text",
},
},
gen_button: {
idx: 11,
name: "prop_11",
type: "string",
value: '"hello"',
valueBuilt: '"hello"',
meta: {
type: "button",
},
},
},
ref_ids: {},
instances: {},
},
originalId: "gxwni8zmj8zhiogfa52eoq6a",
},
],
adv: {
css: "& {\n display: flex;\n\n .rdg-row {\n cursor: pointer;\n }\n\n // &.mobile {}\n // &.desktop {}\n // &:hover {}\n}",
},
};
return { content: res, props: res.childs[0].component.props };
};

97
gen/gen_md/gen_md.ts Executable file
View File

@ -0,0 +1,97 @@
import get from "lodash.get";
import { GFCol as Col, GFCol, formatName } from "../utils";
import { NewFieldArg } from "../gen_form/new_field";
import { gen_header } from "./gen_header";
import { gen_master } from "./gen_master";
import { on_load as table_on_load } from "../gen_table/on_load";
import { on_load as form_on_load } from "../gen_form/on_load";
import { on_submit as form_on_submit } from "../gen_form/on_submit";
import { gen_columns } from "../gen_table/columns";
import { newField as table_new_field } from "../gen_table/new_field";
import { gen_detail } from "./gen_detail";
import { form_before_load } from "./form_before_load";
export const gen_md = (modify: (data: any) => void, data: any) => {
const table = JSON.parse(data.gen_table.value);
const fields = JSON.parse(data.gen_fields.value);
const select = {} as any;
const columns = [] as GFCol[];
const new_fields: NewFieldArg[] = [];
let pk: Col | null = null;
let pks: Record<string, string> = {};
for (let sel of fields) {
if (typeof sel === "object") {
const col = JSON.parse(sel.value) as Col;
select[col.name] = {};
const fields: string[] = [];
for (let s of sel.checked) {
const c = JSON.parse(s) as Col;
if (c.is_pk) {
pks[col.name] = c.name;
fields.push(`::${c.name}`);
select[col.name] = { select: { [c.name]: true } };
col.relation = { table: col.name, pk: sel.name };
} else {
fields.push(c.name);
}
}
columns.push(col);
new_fields.push({
name: col.name,
label: formatName(col.name),
required: false,
type: "relation",
relation: {
table: col.name,
fields,
},
});
} else {
const col = JSON.parse(sel) as Col;
if (col.is_pk) {
pk = col;
} else {
new_fields.push({
name: col.name,
label: formatName(col.name),
required: !col.optional,
type: "text",
});
}
select[col.name] = true;
columns.push(col);
}
}
const result: any = {};
if (pk) {
result["header"] = data["header"];
result["header"].content = gen_header();
result["master"] = data["master"];
const { content, props } = gen_master();
props["columns"].value = gen_columns(columns);
props["on_load"].value = table_on_load({ pk: pk.name, table, select, pks });
props["child"].content.childs = table_new_field(select, pks);
result["master"].content = content;
result["detail"] = data["detail"];
const detail = gen_detail();
const title = JSON.parse(get(data, "title.value"));
const before_load = form_before_load(pk.name, title, "");
detail.props["on_load"].value = form_on_load({ pk, pks, select, table });
detail.props["on_submit"].value = form_on_submit({
pk,
table,
select,
pks,
});
result["detail"].content = detail.content;
}
// modify(result);
alert("Prop Generated!");
};

26
gen/gen_table/columns.ts Executable file
View File

@ -0,0 +1,26 @@
import { GFCol, formatName } from "../utils";
export const gen_columns = (cols: GFCol[]) => {
return `async (): Promise<
{ key: string; name: string; width?: number; frozen?: boolean }[]
> => {
return [
{ key: "id", name: "#", width: 60, frozen: true },
${cols
.filter((e) => {
if (e.is_pk) return false;
return true;
})
.map((e) => {
return (
" " +
JSON.stringify({
key: e.name,
name: formatName(e.name),
})
);
})
.join(",\n")}
];
}`;
};

64
gen/gen_table/gen_table.ts Executable file
View File

@ -0,0 +1,64 @@
import capitalize from "lodash.capitalize";
import { GFCol } from "../utils";
import { gen_columns } from "./columns";
import { newField } from "./new_field";
import { on_load } from "./on_load";
export const gen_table = (modify: (data: any) => void, data: any) => {
const table = JSON.parse(data.gen_table.value) as string;
const fields = JSON.parse(data.gen_fields.value) as (
| string
| { value: string; checked: string[] }
)[];
const select = {} as any;
const columns = [] as GFCol[];
let pk = "";
let pks: Record<string, string> = {};
for (const f of fields) {
if (typeof f === "string") {
const col = JSON.parse(f) as GFCol;
columns.push(col);
select[col.name] = true;
if (col.is_pk) pk = col.name;
} else {
const col = JSON.parse(f.value) as GFCol;
const subsel: any = {};
for (const s of f.checked) {
const sel = JSON.parse(s) as GFCol;
if (sel.is_pk) {
pks[col.name] = sel.name;
col.relation = { table: col.name, pk: sel.name };
}
subsel[sel.name] = true;
}
select[col.name] = { select: subsel };
columns.push(col);
}
}
const result = {} as any;
if (data["columns"]) {
result["columns"] = data["columns"];
result["columns"].value = gen_columns(columns);
}
if (data["on_load"]) {
result["on_load"] = data["on_load"];
result["on_load"].value = on_load({ pk, table, select, pks });
}
if (data["child"]) {
result["child"] = data["child"];
result["child"].content.childs = newField(select, pks);
}
modify(result);
alert("Prop Generated!");
};
const formatName = (name: string) => {
return name
.split("_")
.filter((e) => e.length > 1)
.map((e) => capitalize(e))
.join(" ");
};

89
gen/gen_table/new_field.ts Executable file
View File

@ -0,0 +1,89 @@
import { createId } from "@paralleldrive/cuid2";
import { GFCol } from "../utils";
export const newField = (select: any, pks: Record<string, string>) => {
const result = [];
for (const [k, v] of Object.entries(select) as any) {
if (typeof v === "object") {
const res = Object.keys(v.select)
.filter((e) => e !== pks[k])
.map((e) => `cell.value?.["${e}"]`)
.join('+ " " +');
result.push({
id: createId(),
adv: {
js: `\
<>
{cell.key === "${k}" && (
<div {...props} className={cx(props.className, "")}>
{${res}}
</div>
)}
</>`,
css: "",
jsBuilt: `\
render(
React.createElement(
React.Fragment,
null,
cell.key === "${k}" &&
React.createElement(
"div",
Object.assign({}, props, { className: cx(props.className, "") }),
${res}
)
)
)`,
},
dim: {
h: "full",
w: "full",
},
name: k,
type: "item",
childs: [],
script: {},
});
} else {
result.push({
id: createId(),
adv: {
js: `\
<>
{cell.key === "${k}" && (
<div {...props} className={cx(props.className, "")}>
{cell.value}
</div>
)}
</>`,
css: "",
jsBuilt: `\
render(
React.createElement(
React.Fragment,
null,
cell.key === "${k}" &&
React.createElement(
"div",
Object.assign({}, props, { className: cx(props.className, "") }),
cell.value
)
)
)`,
},
dim: {
h: "full",
w: "full",
},
name: k,
type: "item",
childs: [],
script: {},
});
}
}
return result;
};

43
gen/gen_table/on_load.ts Executable file
View File

@ -0,0 +1,43 @@
import { GFCol } from "../utils";
export const on_load = ({
pk,
table,
select,
pks,
}: {
pk: string;
table: string;
select: any;
pks: Record<string, string>;
}) => {
const sample = {} as any;
for (const [k, v] of Object.entries(select) as any) {
if (typeof v === "object") {
sample[k] = {};
Object.keys(v.select)
.filter((e) => e !== pks[k])
.map((e) => {
sample[k][e] = "sample";
});
} else {
sample[k] = "sample";
}
}
return `\
async () => {
if (isEditor) return [${JSON.stringify(sample)}];
const items = await db.${table}.findMany({
select: ${JSON.stringify(select, null, 2).split("\n").join("\n ")},
orderBy: {
${pk}: "desc"
}
});
return items;
}`;
};

20
gen/utils.ts Executable file
View File

@ -0,0 +1,20 @@
import capitalize from "lodash.capitalize";
export type GFCol = {
name: string;
type: string;
is_pk: boolean;
optional: boolean;
relation?: {
table: string;
pk: string;
};
};
export const formatName = (name: string) => {
return (name || "")
.split("_")
.filter((e) => e.length > 1)
.map((e) => capitalize(e))
.join(" ");
};