This commit is contained in:
rizky 2024-03-26 14:11:22 -07:00
parent 9957e9d626
commit 83351ca7c2
18 changed files with 886 additions and 663 deletions

View File

@ -1,16 +1,17 @@
import { Form as FForm } from "@/comps/ui/form"; import { Form as FForm } from "@/comps/ui/form";
import { Toaster } from "@/comps/ui/sonner"; import { Toaster } from "@/comps/ui/sonner";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { FC } from "react"; import { FC, useEffect } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { FormHook } from "./utils/utils"; import { FormHook } from "./utils/utils";
import { AlertTriangle, Check, Loader2 } from "lucide-react"; import { AlertTriangle, Check, Loader2 } from "lucide-react";
import { cn } from "@/utils"; import { cn } from "@/utils";
import { Skeleton } from "../ui/skeleton";
export const Form: FC<{ export const Form: FC<{
on_init: (arg: { submit: any }) => any; on_init: (arg: { submit: any; reload: any }) => any;
on_load: () => any; on_load: () => any;
on_submit: (arg: { form: any; error: any }) => Promise<any>; on_submit: (arg: { form: any; error: any }) => Promise<any>;
body: any; body: any;
@ -63,7 +64,7 @@ export const Form: FC<{
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" />
Saving ... Processing ...
</>, </>,
{ {
dismissible: true, dismissible: true,
@ -112,7 +113,7 @@ export const Form: FC<{
toast.success( toast.success(
<div className="c-flex c-text-blue-700 c-items-center"> <div className="c-flex c-text-blue-700 c-items-center">
<Check className="c-h-4 c-w-4 c-mr-1 " /> <Check className="c-h-4 c-w-4 c-mr-1 " />
Data saved Done
</div>, </div>,
{ {
className: css` className: css`
@ -127,7 +128,19 @@ export const Form: FC<{
if (!local.init) { if (!local.init) {
local.init = true; local.init = true;
on_init({ submit }); on_init({
submit,
reload: () => {
local.init = false;
form.unload = () => {
form.hook.clearErrors();
form.hook.reset();
delete form.unload;
local.render();
};
local.render();
},
});
const res = on_load(); const res = on_load();
const loaded = (values: any) => { const loaded = (values: any) => {
setTimeout(() => { setTimeout(() => {
@ -166,8 +179,16 @@ export const Form: FC<{
} }
const toaster_el = document.getElementsByClassName("prasi-toaster")[0]; const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
if (form.unload)
return ( return (
<FForm {...form_hook}> <div className="c-p-6 c-flex c-flex-col c-space-y-2 c-w-full c-flex-1 c-items-start">
<Skeleton className="c-h-3 c-w-[50%]" />
<Skeleton className="c-h-3 c-w-[40%]" />
</div>
);
return (
<FormInternal {...form_hook} form={form}>
{toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)} {toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
<form <form
className={ className={
@ -225,6 +246,17 @@ export const Form: FC<{
</PassProp> </PassProp>
</div> </div>
</form> </form>
</FForm> </FormInternal>
); );
}; };
const FormInternal = (props: any) => {
useEffect(() => {
return () => {
if (props.form && props.form.unload) {
props.form.unload();
}
};
}, []);
return <FForm {...props} />;
};

View File

@ -92,7 +92,7 @@ export const Radio: FC<{
{!!local.list && {!!local.list &&
local.list local.list
.filter((e) => e) .filter((e) => e)
.map((item, index:number) => { .map((item, index: number) => {
if (custom === "y" && form) if (custom === "y" && form)
return ( return (
<PassProp <PassProp
@ -103,20 +103,11 @@ export const Radio: FC<{
current_name={name} current_name={name}
item_click={() => { item_click={() => {
if (selection === "single") { if (selection === "single") {
console.log(value, "====single", name);
local.mod(name, { value: item.value }); local.mod(name, { value: item.value });
local.render(); local.render();
} else if (selection === "multi") { } else if (selection === "multi") {
const val = []; local.mod(name, { value: item.value });
console.log();
val.push( ...value, item.value);
local.mod(name, { value: val });
local.render(); local.render();
console.log(item.value, "value", form.hook.getValues());
return;
console.log(value, "====multi", name);
} else { } else {
null; null;
} }

View File

@ -27,4 +27,5 @@ export type FormHook = {
cache: any; cache: any;
validation: Record<string, "required">; validation: Record<string, "required">;
render: () => void; render: () => void;
unload?: () => void;
}; };

View File

@ -1,7 +1,8 @@
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { FC, useEffect } from "react"; import { FC, useEffect, useRef } from "react";
import DataGrid, { import DataGrid, {
ColumnOrColumnGroup, ColumnOrColumnGroup,
DataGridHandle,
Row, Row,
SortColumn, SortColumn,
} from "react-data-grid"; } from "react-data-grid";
@ -14,13 +15,20 @@ type OnRowClick = {
idx: any; idx: any;
event: React.MouseEvent<HTMLDivElement, MouseEvent>; event: React.MouseEvent<HTMLDivElement, MouseEvent>;
}; };
type RowSelected = {
row: any;
rows: any[];
idx: any;
};
export const Table: FC<{ export const Table: FC<{
columns: () => Promise<ColumnOrColumnGroup<any>[]>; columns: () => Promise<ColumnOrColumnGroup<any>[]>;
on_load: () => Promise<any[]>; on_load: (opt: { reload: () => Promise<void> }) => Promise<any[]>;
child: any; child: any;
PassProp: any; PassProp: any;
row_click: (arg: OnRowClick) => void; row_click: (arg: OnRowClick) => void;
}> = ({ columns, on_load, child, PassProp, row_click }) => { selected: (arg: RowSelected) => boolean;
}> = ({ columns, on_load, child, PassProp, row_click, selected }) => {
const local = useLocal({ const local = useLocal({
loading: false, loading: false,
data: undefined as unknown as any[], data: undefined as unknown as any[],
@ -58,7 +66,19 @@ export const Table: FC<{
useEffect(() => { useEffect(() => {
local.loading = true; local.loading = true;
on_load().then((data) => { const arg = {
reload: async () => {
local.loading = true;
local.render();
const data = await on_load(arg);
local.data = data;
local.loading = false;
local.render();
},
};
on_load(arg).then((data) => {
local.data = data; local.data = data;
local.loading = false; local.loading = false;
local.render(); local.render();
@ -67,6 +87,7 @@ export const Table: FC<{
return ( return (
<TableInternal <TableInternal
selected={selected}
row_click={row_click} row_click={row_click}
columns={local.columns} columns={local.columns}
data={local.loading ? undefined : local.data} data={local.loading ? undefined : local.data}
@ -80,7 +101,8 @@ const TableInternal: FC<{
data?: any[]; data?: any[];
render: () => void; render: () => void;
row_click: (arg: OnRowClick) => void; row_click: (arg: OnRowClick) => void;
}> = ({ columns, data, render, row_click }) => { selected: (arg: RowSelected) => boolean;
}> = ({ columns, data, render, row_click, selected }) => {
const local = useLocal({ const local = useLocal({
width: 0, width: 0,
height: 0, height: 0,
@ -91,7 +113,9 @@ const TableInternal: FC<{
}), }),
el: null as any, el: null as any,
sort: [] as SortColumn[], sort: [] as SortColumn[],
scrolled: false,
}); });
const rdg = useRef<null | DataGridHandle>(null);
useEffect(() => { useEffect(() => {
return () => { return () => {
@ -100,22 +124,45 @@ const TableInternal: FC<{
}, []); }, []);
const sort = local.sort; const sort = local.sort;
let sorted = data; // let sorted = data;
if (sort.length > 0 && data) { // if (sort.length > 0 && data) {
sorted = data.sort((a, b) => { // sorted = data.sort((a, b) => {
const va = a[sort[0].columnKey]; // const va = a[sort[0].columnKey];
const vb = b[sort[0].columnKey]; // const vb = b[sort[0].columnKey];
if (typeof va === "string" && typeof vb === "string") { // if (typeof va === "string" && typeof vb === "string") {
if (sort[0].direction === "ASC") { // if (sort[0].direction === "ASC") {
return va.localeCompare(vb); // return va.localeCompare(vb);
} else { // } else {
return vb.localeCompare(va); // return vb.localeCompare(va);
// }
// }
// return 0;
// });
// }
let selected_idx = -1;
if (data) {
for (let i = 0; i < data.length; i++) {
const row = data[i];
if (
typeof selected === "function"
? selected({
idx: i,
row: row,
rows: data,
})
: false
) {
selected_idx = i;
break;
} }
} }
return 0;
});
} }
useEffect(() => {
rdg.current!.scrollToCell({ rowIdx: selected_idx, idx: 0 });
}, [selected_idx]);
return ( return (
<div <div
className={cx( className={cx(
@ -136,6 +183,10 @@ const TableInternal: FC<{
div[aria-selected="true"] { div[aria-selected="true"] {
outline: none; outline: none;
} }
.row-selected {
background: #e2f1ff;
}
` `
)} )}
ref={(el) => { ref={(el) => {
@ -148,6 +199,7 @@ const TableInternal: FC<{
<DataGrid <DataGrid
columns={columns} columns={columns}
sortColumns={sort} sortColumns={sort}
ref={rdg}
onSortColumnsChange={([col]) => { onSortColumnsChange={([col]) => {
local.sort = []; local.sort = [];
if (col) { if (col) {
@ -173,6 +225,8 @@ const TableInternal: FC<{
? undefined ? undefined
: { : {
renderRow(key, props) { renderRow(key, props) {
const is_selected = selected_idx === props.rowIdx;
return ( return (
<Row <Row
key={key} key={key}
@ -187,6 +241,11 @@ const TableInternal: FC<{
}); });
} }
}} }}
isRowSelected={is_selected}
className={cx(
props.className,
is_selected && "row-selected"
)}
/> />
); );
}, },

View File

@ -1,17 +1,18 @@
import { GFCol } from "@/gen/utils";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import get from "lodash.get"; import get from "lodash.get";
import { FC, useEffect } from "react"; import { FC, useEffect } from "react";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
import { import {
MasterDetailConfig, MasterDetailConfig,
MasterDetailLocal, MasterDetailLocal,
MasterDetailProp, MasterDetailProp,
} from "./type"; } from "./type";
import { GFCol } from "@/gen/utils";
import { master_detail_gen_hash, master_detail_params } from "./utils"; import { master_detail_gen_hash, master_detail_params } from "./utils";
export const MasterDetail: FC<MasterDetailProp> = (props) => { export const MasterDetail: FC<MasterDetailProp> = (props) => {
const { header, name, mode, title, actions, gen_fields } = props; const { header, name, mode, title, actions, gen_fields, md_parent } = props;
const md = useLocal<MasterDetailLocal & { cache_internal: any }>( const md = useLocal<MasterDetailLocal & { cache_internal: any }>(
{ {
name, name,
@ -28,25 +29,53 @@ export const MasterDetail: FC<MasterDetailProp> = (props) => {
cache_internal: {}, cache_internal: {},
cache: null as any, cache: null as any,
pk: null as null | GFCol, pk: null as null | GFCol,
parent: md_parent,
}, },
() => { () => {
if (!isEditor) { const params = master_detail_params(md);
const hash = master_detail_params(md); const hash = params.hash;
if (params.tabs && params.tabs[md.name]) {
md.active_tab = params.tabs[md.name];
}
if (md.mode === "breadcrumb") {
if (hash && hash[name] && md.pk) { if (hash && hash[name] && md.pk) {
if (md.pk.type === "int") { if (md.pk.type === "int") {
md.selected = { [md.pk.name]: parseInt(hash[name]) }; md.selected = { [md.pk.name]: parseInt(hash[name]) };
} else { } else {
md.selected = { [md.pk.name]: hash[name] }; md.selected = { [md.pk.name]: hash[name] };
} }
}
}
md.render(); md.render();
} }
}
}
); );
const local = useLocal({ init: false }, () => {
local.init = true;
local.render();
});
if (local.init) {
const params = master_detail_params(md);
const hash = params.hash;
delete hash.parent_id;
if (!md.selected) {
delete hash[md.name];
master_detail_gen_hash(params);
} else if (md.pk) {
hash[md.name] = md.selected[md.pk.name];
master_detail_gen_hash(params);
}
}
if (!md.pk && gen_fields) { if (!md.pk && gen_fields) {
for (const str of gen_fields) { for (const str of gen_fields) {
try {
const f = JSON.parse(str) as GFCol; const f = JSON.parse(str) as GFCol;
if (f.is_pk) md.pk = f; if (f.is_pk) md.pk = f;
} catch (e) {}
} }
} }
@ -67,6 +96,11 @@ export const MasterDetail: FC<MasterDetailProp> = (props) => {
md.ui.title = title; md.ui.title = title;
md.render(); md.render();
}, [title]); }, [title]);
useEffect(() => {
md.mode = mode;
md.render();
}, [mode]);
} }
useEffect(() => { useEffect(() => {
@ -85,9 +119,89 @@ export const MasterDetail: FC<MasterDetailProp> = (props) => {
className={cx( className={cx(
"c-flex-1 c-flex-col c-flex c-w-full c-h-full c-overflow-hidden" "c-flex-1 c-flex-col c-flex c-w-full c-h-full c-overflow-hidden"
)} )}
>
{md.mode === "breadcrumb" && (
<BreadcrumbMode props={props} md={md} header={header} />
)}
{md.mode === "vertical" && (
<VerticalMode props={props} md={md} header={header} />
)}
{md.mode === "horizontal" && (
<HorizontalMode props={props} md={md} header={header} />
)}
{isEditor && (
<div className="c-hidden">
<props.PassProp md={md}>{props.detail}</props.PassProp>
</div>
)}
</div>
);
};
const VerticalMode: FC<{
props: MasterDetailProp;
md: MasterDetailConfig;
header: any;
}> = ({ props, md, header }) => {
return (
<div className={cx("c-flex-1")}>
<PanelGroup direction="vertical">
<Panel className="c-border-b">
<props.PassProp md={md}>{props.master}</props.PassProp>
</Panel>
<>
<PanelResizeHandle />
<Panel
className="c-flex c-flex-col c-items-stretch"
defaultSize={
parseInt(localStorage.getItem(`prasi-md-h-${md.name}`) || "") ||
undefined
}
onResize={(e) => {
if (e < 80) {
localStorage.setItem(`prasi-md-h-${md.name}`, e.toString());
}
}}
> >
<props.PassProp md={md}>{header}</props.PassProp> <props.PassProp md={md}>{header}</props.PassProp>
<BreadcrumbMode props={props} md={md} /> <Detail props={props} md={md} />
</Panel>
</>
</PanelGroup>
</div>
);
};
const HorizontalMode: FC<{
props: MasterDetailProp;
md: MasterDetailConfig;
header: any;
}> = ({ props, md, header }) => {
return (
<div className={cx("c-flex-1")}>
<PanelGroup direction="horizontal">
<Panel className="c-border-r">
<props.PassProp md={md}>{props.master}</props.PassProp>
</Panel>
<>
<PanelResizeHandle />
<Panel
className="c-flex c-flex-col c-items-stretch"
defaultSize={
parseInt(localStorage.getItem(`prasi-md-h-${md.name}`) || "") ||
undefined
}
onResize={(e) => {
if (e < 80) {
localStorage.setItem(`prasi-md-h-${md.name}`, e.toString());
}
}}
>
<props.PassProp md={md}>{header}</props.PassProp>
<Detail props={props} md={md} />
</Panel>
</>
</PanelGroup>
</div> </div>
); );
}; };
@ -95,39 +209,23 @@ export const MasterDetail: FC<MasterDetailProp> = (props) => {
const BreadcrumbMode: FC<{ const BreadcrumbMode: FC<{
props: MasterDetailProp; props: MasterDetailProp;
md: MasterDetailConfig; md: MasterDetailConfig;
}> = ({ props, md }) => { header: any;
const local = useLocal({ init: false }, () => { }> = ({ props, md, header }) => {
local.init = true;
local.render();
});
if (local.init) {
const hash = master_detail_params(md);
delete hash.parent_id;
if (!md.selected) {
delete hash[md.name];
location.hash = master_detail_gen_hash(hash);
} else if (md.pk) {
hash[md.name] = md.selected[md.pk.name];
location.hash = master_detail_gen_hash(hash);
}
}
return ( return (
<>
<props.PassProp md={md}>{header}</props.PassProp>
<div className={cx("c-flex-1 c-flex-col c-flex")}> <div className={cx("c-flex-1 c-flex-col c-flex")}>
<div <div
className={cx(md.selected && "c-hidden", "c-flex c-flex-1 c-flex-col")} className={cx(
md.selected && "c-hidden",
"c-flex c-flex-1 c-flex-col"
)}
> >
<props.PassProp md={md}>{props.master}</props.PassProp> <props.PassProp md={md}>{props.master}</props.PassProp>
</div> </div>
{md.selected && <Detail props={props} md={md} />} {md.selected && <Detail props={props} md={md} />}
{isEditor && !local.init && (
<div className="c-hidden">
<Detail props={props} md={md} />
</div>
)}
</div> </div>
</>
); );
}; };
@ -145,7 +243,9 @@ const Detail: FC<{
); );
if (idx < 0) { if (idx < 0) {
idx = 0; idx = 0;
if (childs[idx]) {
md.active_tab = childs[idx].name; md.active_tab = childs[idx].name;
}
setTimeout(md.render); setTimeout(md.render);
} }
const content = childs[idx]; const content = childs[idx];
@ -170,6 +270,7 @@ const Detail: FC<{
"c-flex c-w-full c-rounded-none c-border-b c-border-gray-300 c-justify-start", "c-flex c-w-full c-rounded-none c-border-b c-border-gray-300 c-justify-start",
css` css`
padding: 0 !important; padding: 0 !important;
padding-left: 20px !important;
height: auto !important; height: auto !important;
` `
)} )}
@ -179,6 +280,13 @@ const Detail: FC<{
<TabsTrigger <TabsTrigger
value={e.name} value={e.name}
onClick={() => { onClick={() => {
const params = master_detail_params(md);
const hash = params.hash;
delete hash.parent_id;
params.tabs[md.name] = e.name;
master_detail_gen_hash(params);
md.active_tab = e.name; md.active_tab = e.name;
md.render(); md.render();
}} }}

View File

@ -11,6 +11,7 @@ export type MasterDetailProp = {
actions: (md: any) => MasterDetailAction[]; actions: (md: any) => MasterDetailAction[];
gen_fields: any; gen_fields: any;
name: string; name: string;
md_parent?: MasterDetailConfig;
}; };
type MasterDetailAction = { type MasterDetailAction = {
@ -34,6 +35,7 @@ export type MasterDetailLocal = {
}; };
cache: (name: string, opt?: { reset: boolean }) => any; cache: (name: string, opt?: { reset: boolean }) => any;
pk: null | GFCol; pk: null | GFCol;
parent?: MasterDetailConfig;
}; };
export type MasterDetailConfig = MasterDetailLocal & { render: () => void }; export type MasterDetailConfig = MasterDetailLocal & { render: () => void };
@ -59,7 +61,8 @@ export const master_detail_typings = {
actions: ${action_type}[]; actions: ${action_type}[];
}; };
cache: (name: string, opt?: { reset: boolean }) => any; cache: (name: string, opt?: { reset: boolean }) => any;
pk: null | any pk: null | any;
parent?: typeof md;
}`, }`,
action_type, action_type,
}; };

View File

@ -7,27 +7,52 @@ export const master_detail_params = (md: MasterDetailConfig) => {
: md.selected?.[md.pk?.name || ""]; : md.selected?.[md.pk?.name || ""];
const hash: any = {}; const hash: any = {};
for (const h of location.hash.split("#")) { const tabs: any = {};
let raw_hash = decodeURIComponent(location.hash);
if (isEditor) {
raw_hash = localStorage.getItem("prasi-md-hash") || "";
}
for (const h of raw_hash.split("#")) {
if (h) { if (h) {
if (h.includes("=")) {
const [tab_name, tab_val] = h.split("="); const [tab_name, tab_val] = h.split("=");
if (tab_name && tab_val) { if (tab_name && tab_val) {
hash[tab_name] = tab_val; hash[tab_name] = tab_val;
} }
} else if (h.includes("~")) {
const [tab_name, tab_val] = h.split("~");
if (tab_name && tab_val) {
tabs[tab_name] = tab_val;
}
}
} }
} }
if (parent_id) { if (parent_id) {
return { ...hash, parent_id }; return { hash, parent_id, tabs };
} }
return hash; return { hash, tabs };
}; };
export const master_detail_gen_hash = ( export const master_detail_gen_hash = (obj: {
obj: Record<string, number | string> hash: Record<string, number | string>;
) => { tabs: Record<string, string>;
parent_id?: string;
}) => {
let hash = ""; let hash = "";
for (const [k, v] of Object.entries(obj)) { for (const [k, v] of Object.entries(obj.hash)) {
hash += `#${k}=${v}`; hash += `#${k}=${v}`;
} }
return hash; for (const [k, v] of Object.entries(obj.tabs)) {
hash += `#${k}~${v}`;
}
if (isEditor) {
localStorage.setItem("prasi-md-hash", hash);
} else {
location.hash = hash;
}
}; };

View File

@ -10,18 +10,22 @@ const TabsList = React.forwardRef<
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & { React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {
overrideClassName?: boolean; overrideClassName?: boolean;
} }
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => {
const overrideClassName = props.overrideClassName;
delete props.overrideClassName;
return (
<TabsPrimitive.List <TabsPrimitive.List
ref={ref} ref={ref}
className={cn( className={cn(
props.overrideClassName === true 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", : "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<
@ -29,18 +33,22 @@ const TabsTrigger = React.forwardRef<
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & { React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & {
overrideClassName?: boolean; overrideClassName?: boolean;
} }
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => {
const overrideClassName = props.overrideClassName;
delete props.overrideClassName;
return (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
props.overrideClassName === true 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", : "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<

View File

@ -66,6 +66,7 @@ export const gen_form = (modify: (data: any) => void, data: any) => {
return; return;
} }
if (pk) { if (pk) {
if (mode) {
if (mode.includes("on_load")) { if (mode.includes("on_load")) {
result["on_load"] = data["on_load"]; result["on_load"] = data["on_load"];
result["on_load"].value = on_load({ pk, pks, select, table }); result["on_load"].value = on_load({ pk, pks, select, table });
@ -75,6 +76,7 @@ export const gen_form = (modify: (data: any) => void, data: any) => {
result["on_submit"] = data["on_submit"]; result["on_submit"] = data["on_submit"];
result["on_submit"].value = on_submit({ pk, table, select, pks }); result["on_submit"].value = on_submit({ pk, table, select, pks });
} }
}
result["body"] = data["body"]; result["body"] = data["body"];
const childs = get(result, "body.content.childs"); const childs = get(result, "body.content.childs");

View File

@ -36,6 +36,10 @@ export const on_submit = ({
if (res) form.${id} = res.${id}; if (res) form.${id} = res.${id};
} }
if (md.mode !== "breadcrumb") {
md.cache("master")?.reload();
}
return true; return true;
}; };

View File

@ -7,6 +7,9 @@ export const form_before_load = (
label: string label: string
) => { ) => {
return ` return `
const master = md.cache("master") as
| { reload: () => Promise<void> }
| undefined;
const id = master_detail_params(md).parent_id; const id = master_detail_params(md).parent_id;
@ -31,6 +34,9 @@ export const form_before_load = (
md.ui.back = false; md.ui.back = false;
md.selected = null; md.selected = null;
md.render(); md.render();
if (md.mode !== "breadcrumb") {
master?.reload();
}
}); });
} }
}, },
@ -41,10 +47,13 @@ export const form_before_load = (
onClick: async () => { onClick: async () => {
md.ui.actions = [{ label: "Saving...", type: "ghost" }]; md.ui.actions = [{ label: "Saving...", type: "ghost" }];
md.render(); md.render();
await md.cache("form")._submit(); await md.cache("master")._submit();
setTimeout(() => { setTimeout(() => {
set_actions(); set_actions();
md.render(); md.render();
if (md.mode !== "breadcrumb") {
master?.reload();
}
}, 500); }, 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>\`, 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>\`,

View File

@ -18,6 +18,31 @@ export const gen_detail = () => {
w: "full", w: "full",
h: "full", h: "full",
}, },
adv: {
css: "",
js: "",
jsBuilt: "render();\n",
},
childs: [
{
id: createId(),
name: "selected",
type: "item",
dim: {
w: "full",
h: "full",
},
adv: {
css: "",
js: `<>
{(md.selected || isEditor) && (
<div {...props} className={cx(props.className, "")}>
{children}
</div>
)}
</>`,
jsBuilt: `render(React.createElement(React.Fragment, null, (md.selected || isEditor) && (React.createElement("div", Object.assign({}, props, { className: cx(props.className, "") }), children))));`,
},
childs: [ childs: [
{ {
id: createId(), id: createId(),
@ -257,7 +282,8 @@ export const gen_detail = () => {
ref_ids: {}, ref_ids: {},
instances: { instances: {
npj543t5rpwx932a153hsfyl: { npj543t5rpwx932a153hsfyl: {
zmit41kbgkbqcsmm8aspp8zy: "mo4m0rnyey4y6v7tlajod95l", zmit41kbgkbqcsmm8aspp8zy:
"mo4m0rnyey4y6v7tlajod95l",
}, },
}, },
}, },
@ -417,10 +443,14 @@ export const gen_detail = () => {
}, },
name: "prop_5", name: "prop_5",
type: "string", type: "string",
value: value: `\
'({ submit }: Init) => {\n // on init\n md.cache("form")._submit = submit;\n};\n\ntype Init = { submit: () => void }', ({ submit, reload }: Init) => {
valueBuilt: // on init
' ({ submit }) => {\n md.cache("form")._submit = submit;\n};\n', md.cache("detail").submit = submit;
md.cache("detail").reload = reload;
};
type Init = { submit: () => void; reload: () => Promise<void> };`,
}, },
on_load: { on_load: {
idx: 1, idx: 1,
@ -479,11 +509,8 @@ export const gen_detail = () => {
originalid: createId(), originalid: createId(),
}, },
], ],
adv: {
css: "",
js: "",
jsBuilt: "render();\n",
}, },
],
}, },
], ],
adv: { adv: {
@ -495,6 +522,6 @@ export const gen_detail = () => {
return { return {
content: res.childs[0], content: res.childs[0],
props: res.childs[0].childs[0].component.props, props: res.childs[0].childs[0].childs[0].component.props,
}; };
}; };

View File

@ -13,7 +13,7 @@ export const gen_header = () => {
childs: [ childs: [
{ {
id: createId(), id: createId(),
name: "breadcrumb", name: "wrapper",
type: "item", type: "item",
dim: { dim: {
w: "full", w: "full",
@ -71,9 +71,9 @@ export const gen_header = () => {
}, },
], ],
adv: { 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</>', js: '<>\n {md.selected && (\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: 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', 'render(/* @__PURE__ */ React.createElement(React.Fragment, null, md.selected && /* @__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}", css: "& {\n display: flex;\n cursor: pointer;\n\n // &.mobile {}\n // &.desktop {}\n &:hover {\n background: rgb(237, 246, 255);\n }\n}",
}, },
script: {}, script: {},
@ -188,9 +188,6 @@ export const gen_header = () => {
}, },
], ],
adv: { 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: "", css: "",
}, },
script: {}, script: {},

View File

@ -100,110 +100,7 @@ export const gen_master = () => {
}, },
name: "prop_3", name: "prop_3",
type: "item", type: "item",
childs: [ childs: [] as any,
{
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, hidden: false,
}, },
typings: typings:
@ -217,7 +114,7 @@ export const gen_master = () => {
name: "prop_1", name: "prop_1",
type: "string", type: "string",
value: 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 }', 'async (): Promise<\n { key: string; name: string; width?: number; frozen?: boolean }[]\n > => {\n return [\n { key: "id", name: "#", width: 60, frozen: true },\n {"key":"m_cabang","name":"Cabang"},\n {"key":"m_regional","name":"Regional"},\n {"key":"nama_aset_komersial","name":"Nama Aset Komersial"},\n {"key":"asset_number","name":"Asset Number"}\n ];\n }',
}, },
on_load: { on_load: {
idx: 1, idx: 1,
@ -227,7 +124,7 @@ export const gen_master = () => {
name: "prop_1", name: "prop_1",
type: "string", type: "string",
value: value:
"async () => {\n if (isEditor)\n return [\n {\n },\n });\n\n return items;\n}", 'async (arg: TableOnLoad) => {\n md.cache("master").reload = arg.reload;\n\n if (isEditor)\n return [\n {\n id: "sample",\n m_cabang: { nama_cabang: "sample" },\n m_regional: { regional: "sample" },\n nama_aset_komersial: "sample",\n asset_number: "sample",\n },\n ];\n\n const items = await db.m_aset.findMany({\n select: {\n id: 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 nama_aset_komersial: true,\n asset_number: true,\n },\n orderBy: {\n id: createId(),\n },\n });\n\n return items;\n};\n\ntype TableOnLoad = {\n reload: () => Promise<void>;\n}',
}, },
generate: { generate: {
idx: 5, idx: 5,
@ -263,10 +160,32 @@ export const gen_master = () => {
idx: 11, idx: 11,
name: "prop_11", name: "prop_11",
type: "string", type: "string",
value: 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}", ({ row, rows, idx, event }: OnRowClick) => {
valueBuilt: md.selected = row;
" ({ row, rows, idx, event }) => {\n md.selected = row;\n md.render();\n};\n", md.render();
const reload = md.cache("detail")?.reload;
if (typeof reload === 'function') {
reload()
}
};
type OnRowClick = {
row: any;
rows: any[];
idx: any;
event: React.MouseEvent<HTMLDivElement, MouseEvent>;
}`,
valueBuilt: `({ row, rows, idx, event }) => {
var _a;
md.selected = row;
md.render();
const reload = (_a = md.cache("detail")) === null || _a === void 0 ? void 0 : _a.reload;
if (typeof reload === 'function') {
reload();
}
};
`,
meta: { meta: {
type: "text", type: "text",
}, },
@ -281,6 +200,18 @@ export const gen_master = () => {
type: "button", type: "button",
}, },
}, },
selected: {
idx: 3,
name: "prop_9",
type: "string",
value:
"({ row, rows, idx }: SelectedRow) => {\n return md.selected?.id === row?.id;\n};\n\ntype SelectedRow = {\n row: any;\n rows: any[];\n idx: any;\n}",
valueBuilt:
" ({ row, rows, idx }) => {\n return md.selected?.id === row?.id;\n};\n",
meta: {
type: "text",
},
},
}, },
ref_ids: {}, ref_ids: {},
instances: {}, instances: {},

View File

@ -28,17 +28,21 @@ export const gen_md = (modify: (data: any) => void, data: any) => {
const col = JSON.parse(sel.value) as Col; const col = JSON.parse(sel.value) as Col;
select[col.name] = {}; select[col.name] = {};
const fields: string[] = []; const fields: string[] = [];
const subsel: any = {};
for (let s of sel.checked) { for (let s of sel.checked) {
const c = JSON.parse(s) as Col; const c = JSON.parse(s) as Col;
if (c.is_pk) { if (c.is_pk) {
pks[col.name] = c.name; pks[col.name] = c.name;
fields.push(`::${c.name}`); fields.push(`::${c.name}`);
select[col.name] = { select: { [c.name]: true } };
col.relation = { table: col.name, pk: sel.name }; col.relation = { table: col.name, pk: sel.name };
} else { } else {
fields.push(c.name); fields.push(c.name);
} }
subsel[c.name] = true;
} }
select[col.name] = { select: subsel };
columns.push(col); columns.push(col);
new_fields.push({ new_fields.push({
name: col.name, name: col.name,

View File

@ -56,6 +56,19 @@ export const gen_table = (modify: (data: any) => void, data: any) => {
result["child"] = data["child"]; result["child"] = data["child"];
result["child"].content.childs = newField(select, pks); result["child"].content.childs = newField(select, pks);
} }
if (data["selected"]) {
result["selected"] = data["selected"];
result["selected"].value = `\
({ row, rows, idx }: SelectedRow) => {
return md.selected?.id === row?.id;
};
type SelectedRow = {
row: any;
rows: any[];
idx: any;
}`;
}
} }
modify(result); modify(result);

View File

@ -1,5 +1,4 @@
import { createId } from "@paralleldrive/cuid2"; import { createId } from "@paralleldrive/cuid2";
import { GFCol } from "../utils";
export const newField = (select: any, pks: Record<string, string>) => { export const newField = (select: any, pks: Record<string, string>) => {
const result = []; const result = [];
@ -8,7 +7,7 @@ export const newField = (select: any, pks: Record<string, string>) => {
if (typeof v === "object") { if (typeof v === "object") {
const res = Object.keys(v.select) const res = Object.keys(v.select)
.filter((e) => e !== pks[k]) .filter((e) => e !== pks[k])
.map((e) => `cell.value?.["${e}"]`) .map((e) => `cell.value?.["${k}"]?.["${e}"] || ''`)
.join('+ " " +'); .join('+ " " +');
result.push({ result.push({
@ -18,7 +17,7 @@ export const newField = (select: any, pks: Record<string, string>) => {
<> <>
{cell.key === "${k}" && ( {cell.key === "${k}" && (
<div {...props} className={cx(props.className, "")}> <div {...props} className={cx(props.className, "")}>
{${res}} {Array.isArray(cell.value) ? cell.value.length + ' items' : ${res}}
</div> </div>
)} )}
</>`, </>`,

View File

@ -28,7 +28,12 @@ export const on_load = ({
} }
return `\ return `\
async () => { async (arg: TableOnLoad) => {
if (typeof md !== 'undefined') {
md.cache("master").reload = arg.reload;
}
if (isEditor) return [${JSON.stringify(sample)}]; if (isEditor) return [${JSON.stringify(sample)}];
const items = await db.${table}.findMany({ const items = await db.${table}.findMany({
@ -39,5 +44,10 @@ async () => {
}); });
return items; return items;
}`; }
type TableOnLoad = {
reload: () => Promise<void>;
}
`;
}; };