fix master detail

This commit is contained in:
rizrmd 2024-05-19 02:47:23 -07:00
parent 12f6164682
commit e096b9dd34
33 changed files with 159 additions and 1419 deletions

View File

@ -1,5 +1,5 @@
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { FC, ReactNode } from "react"; import { FC, ReactNode, useEffect } from "react";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
export type BreadItem = { export type BreadItem = {
@ -8,85 +8,55 @@ export type BreadItem = {
onClick?: (ev: any) => void; onClick?: (ev: any) => void;
}; };
const breadcrumbData = {} as Record<string, any>;
type BreadcrumbProps = { type BreadcrumbProps = {
on_load?: () => Promise<BreadItem[]>; on_load?: () => Promise<BreadItem[]>;
className?: string; className?: string;
props?: any;
value?: BreadItem[]; value?: BreadItem[];
item?: any item?: PrasiItem;
}; };
export const Breadcrumb: FC<BreadcrumbProps> = (_arg) => { export const Breadcrumb: FC<BreadcrumbProps> = ({
const { on_load, item } = _arg; value,
className,
on_load,
item,
}) => {
const local = useLocal({ const local = useLocal({
list: _arg.value || ([] as BreadItem[]), list: [] as BreadItem[],
status: "init" as "init" | "loading" | "ready", status: "init" as "init" | "loading" | "ready",
params: {}, params: {},
}); });
// code review: md.breadcrumb yang di set di props value dipindahkan di on_load
// dan ketika panjang _arg.value bernilai null maka status berubah menjadi init useEffect(() => {
// untuk menjalankan on_load if (value) {
// case: ketika refreshBread dijalankan pada MasterDetail local.list = value;
// md.breadcrum yang awalnya array kosong akan berisi satu array namun
// md.breadcrumb yang diterima di komponen ini tidak berubah tetap seperti
// value default yakni array kosong
if (_arg.value) {
local.list = _arg.value;
local.status = "ready"; local.status = "ready";
if(!_arg.value.length) local.status = "init"
}
if (local.status === "init") {
let should_load = true;
local.status = "loading";
if (isEditor && item && breadcrumbData[item.id]) {
local.list = breadcrumbData[item.id];
local.status = "ready";
should_load = false;
if(!local.list.length) should_load = true;
}
if (should_load && typeof on_load === "function") {
const callback = (res: any) => {
local.list = res;
if (item) breadcrumbData[item.id] = res;
local.status = "ready";
};
const res = on_load();
if (res instanceof Promise) {
res.then((res) => {
callback(res);
local.render();
});
} else callback(res);
}
} }
if (isEditor && local.status !== "ready") { if (typeof on_load === "function") {
if (item && breadcrumbData[item.id]) { local.status = "loading";
local.list = breadcrumbData[_arg.item.id]; on_load().then((list) => {
} local.list = list;
local.status = "ready"; local.status = "ready";
local.render();
});
} }
local.render();
}, []);
return ( return (
<div <div
className={cx( className={cx(
"breadcrumb c-w-full c-flex c-items-center c-flex-wrap", "breadcrumb c-w-full c-flex c-items-center c-flex-wrap",
_arg.className className
)} )}
> >
{local.status !== "ready" ? ( {local.status !== "ready" ? (
<Skeleton className="c-h-4 c-w-[80%]" /> <Skeleton className="c-h-4 c-w-[80%]" />
) : ( ) : (
<> <>
{local.list === null ? ( {local.list &&
<>
<h1 className="c-font-semibold c-text-xs md:c-text-base">
Null Breadcrumb
</h1>
</>
) : (
(local.list || []).map((item, index): ReactNode => { (local.list || []).map((item, index): ReactNode => {
const lastIndex = local.list.length - 1; const lastIndex = local.list.length - 1;
@ -128,8 +98,7 @@ export const Breadcrumb: FC<BreadcrumbProps> = (_arg) => {
)} )}
</> </>
); );
}) })}
)}
</> </>
)} )}
</div> </div>

View File

@ -1,58 +0,0 @@
import { FMLocal } from "../typings";
import { BaseLabel } from "./BaseLabel";
import { BaseFieldProps } from "./utils/type/field";
import { useField } from "./utils/use-field";
export const BaseField = <T extends Record<string, any>>(
arg: BaseFieldProps<T> & { fm: FMLocal }
) => {
const field = useField<T>(arg);
const fm = arg.fm;
const mode = fm.props.label_mode || "vertical";
const props = arg.props;
const w = field.width;
const errors = fm.error.get(field.name);
return (
<label
className={cx(
"field",
"c-flex",
css`
padding: 5px 0px 0px 10px;
`,
w === "auto" && fm.size.field === "full" && "c-w-full",
w === "auto" && fm.size.field === "half" && "c-w-1/2",
w === "full" && "c-w-full",
w === "¾" && "c-w-3/4",
w === "½" && "c-w-1/2",
w === "⅓" && "c-w-1/3",
w === "¼" && "c-w-1/4",
mode === "horizontal" && "c-flex-row c-items-center",
mode === "vertical" && "c-flex-col c-space-y-1"
)}
{...props}
>
{mode !== "hidden" && <BaseLabel field={field} fm={fm} />}
<div className="field-inner c-flex c-flex-1 c-flex-col">
{field.desc && (
<div className={cx("c-p-2 c-text-xs", errors.length > 0 && "c-pb-1")}>
{field.desc}
</div>
)}
{errors.length > 0 && (
<div
className={cx(
"c-p-2 c-text-xs c-text-red-600",
field.desc && "c-pt-0"
)}
>
{errors.map((err) => {
return <div>{err}</div>;
})}
</div>
)}
</div>
</label>
);
};

View File

@ -1,51 +0,0 @@
import { useLocal } from "@/utils/use-local";
import { FC } from "react";
import { BaseField } from "./BaseField";
import { initSimpleForm as initBaseForm } from "./utils/init";
import { BaseFieldProps, BaseFormProps } from "./utils/type/field";
type Children<T extends Record<string, any>> = Exclude<
BaseFormProps<T>["children"],
undefined
>;
export const BaseForm = <T extends Record<string, any>>(
arg: BaseFormProps<T>
) => {
const fm = useLocal<FMInternal>({ status: "init" } as any);
const local = useLocal({
Field: null as null | FC<BaseFieldProps<T>>,
children: null as unknown as Children<T>,
});
if (fm.status === "init") {
local.Field = (props) => {
return <BaseField fm={fm} {...props} />;
};
if (arg.children) local.children = arg.children;
else {
local.children = ({ Field }) => {
const data = Object.entries(fm.data);
return (
<>
{data.map(([key, value]) => {
return <Field name={key} />;
})}
</>
);
};
}
initBaseForm(fm, arg);
}
return (
<form
onSubmit={(e) => {
e.preventDefault();
fm.submit();
}}
>
{local.Field && local.children({ Field: local.Field })}
</form>
);
};

View File

@ -1,49 +0,0 @@
import { FC } from "react";
import { BaseFieldLocal } from "./utils/type/field";
import { FMLocal } from "../typings";
export const BaseLabel = <T extends Record<string, any>>({
field,
fm,
}: {
field: BaseFieldLocal<T>;
fm: FMLocal;
}) => {
const errors = fm.error.get(field.name);
return (
<div
className={cx(
"label c-text-sm c-flex c-items-center",
fm.props.label_mode === "horizontal" &&
css`
width: ${fm.props.label_width}px;
`,
fm.props.label_mode === "vertical" && "c-mt-3"
)}
>
<span className={cx(errors.length > 0 && `c-text-red-600`)}>
{field.label}
</span>
{field.required && (
<span className="c-text-red-600 c-mb-2 c-ml-1">
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 6v12" />
<path d="M17.196 9 6.804 15" />
<path d="m6.804 9 10.392 6" />
</svg>
</span>
)}
</div>
);
};

View File

@ -1,35 +0,0 @@
import { FMLocal } from "../../typings";
import { formError } from "../../utils/error";
import { reloadBaseForm } from "./reload";
import { submitBaseForm } from "./submit";
import { BaseFormProps } from "./type/field";
export const initSimpleForm = <T extends Record<string, any>>(
fm: FMLocal,
arg: BaseFormProps<T>
) => {
fm.data = {};
fm.error = formError(fm);
fm.events = {
on_change(name, new_value) {},
};
fm.field_def = {};
fm.fields = {};
fm.internal = {
submit: { done: [], promises: [], timeout: null as any },
reload: { done: [], promises: [], timeout: null as any },
};
fm.props = {} as any;
fm.reload = async () => {
await reloadBaseForm(fm, arg);
};
fm.size = {
width: 0,
height: 0,
field: "full",
};
fm.submit = () => {
return submitBaseForm(fm, arg);
};
fm.status = "ready";
};

View File

@ -1,7 +0,0 @@
import { FMLocal } from "../../typings";
import { BaseFormProps } from "./type/field";
export const reloadBaseForm = async <T extends Record<string, any>>(
fm: FMLocal,
arg: BaseFormProps<T>
) => {};

View File

@ -1,9 +0,0 @@
import { FMLocal } from "../../typings";
import { BaseFormProps } from "./type/field";
export const submitBaseForm = async <T extends Record<string, any>>(
fm: FMLocal,
arg: BaseFormProps<T>
) => {
return true;
};

View File

@ -1,57 +0,0 @@
import { FC, ReactNode } from "react";
export type BaseFieldWidth =
| "auto"
| "full"
| "¾"
| "½"
| "⅓"
| "¼"
| "1/2"
| "1/3"
| "1/4"
| "3/4";
export type BaseFieldType = "text" | "relation";
export type BaseFieldProps<T extends Record<string, any>> = {
name: keyof T;
label?: string;
type?: BaseFieldType;
props?: any;
desc?: string;
on_change?: (arg: { value: any }) => void | Promise<void>;
prefix?: any;
suffix?: any;
required?: boolean;
required_msg?: (name: string) => string;
disabled?: boolean;
width?: BaseFieldWidth;
};
export type BaseFieldInternal<T extends Record<string, any>> = {
name: keyof T;
label: ReactNode;
type: BaseFieldType;
desc: string;
on_change: (arg: { value: any }) => void | Promise<void>;
prefix: any;
suffix: any;
required: boolean;
required_msg: (name: string) => string;
disabled: boolean;
width: BaseFieldWidth;
status: "init" | "loading" | "ready";
PassProp: any;
child: any;
};
export type BaseFieldLocal<T extends Record<string, any>> =
BaseFieldInternal<T>;

View File

@ -1,8 +0,0 @@
import { FC, ReactNode } from "react";
import { BaseFieldProps } from "./field";
export type BaseFormProps<T extends Record<string, any>> = {
onLoad: () => Promise<T | null>;
onSubmit: (arg: { data: T | null }) => Promise<boolean>;
children?: (arg: { Field: FC<BaseFieldProps<T>> }) => ReactNode;
};

View File

@ -1,28 +0,0 @@
import { useLocal } from "@/utils/use-local";
import { FMLocal } from "../../typings";
import {
BaseFieldInternal,
BaseFieldLocal,
BaseFieldProps,
} from "./type/field";
import { formatName } from "@/gen/utils";
export const useField = <T extends Record<string, any>>(
arg: BaseFieldProps<T> & { fm: FMLocal }
) => {
const fm = arg.fm;
const local = useLocal<BaseFieldInternal<T>>({
status: "init",
} as any) as BaseFieldLocal<T>;
if (local.status === "init") {
local.name = arg.name;
local.label = arg.label || formatName(arg.name as string);
local.type = arg.type || "text";
local.desc = arg.desc || "";
local.status = "ready";
local.width = arg.width || fm.size.field === "full" ? "full" : "1/2";
}
return local as BaseFieldLocal<T>;
};

View File

@ -7,10 +7,9 @@ import { Label } from "./Label";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
export const Field: FC<FieldProp> = (arg) => { export const Field: FC<FieldProp> = (arg) => {
console.log({arg})
const { fm } = arg; const { fm } = arg;
const field = useField(arg); const field = useField(arg);
const name = typeof field.name === 'function' ? field.name() : field.name; const name = field.name;
const local = useLocal({ prev_val: fm.data[name] }); const local = useLocal({ prev_val: fm.data[name] });
const mode = fm.props.label_mode; const mode = fm.props.label_mode;
@ -56,9 +55,7 @@ export const Field: FC<FieldProp> = (arg) => {
fm={fm} fm={fm}
PassProp={arg.PassProp} PassProp={arg.PassProp}
child={arg.child} child={arg.child}
_meta={arg._meta}
_item={arg._item} _item={arg._item}
_sync={arg._sync}
arg={arg} arg={arg}
/> />
{field.desc && ( {field.desc && (

View File

@ -1,16 +1,9 @@
import { createItem } from "@/gen/utils"; import { FC, isValidElement } from "react";
import get from "lodash.get"; import { FMLocal, FieldLocal, FieldProp } from "../typings";
import { FC, isValidElement, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp, FieldTypeCustom } from "../typings";
import { genFieldMitem, updateFieldMItem } from "../utils/gen-mitem";
import { fieldMapping } from "./mapping";
import { FieldLoading } from "./raw/FieldLoading"; import { FieldLoading } from "./raw/FieldLoading";
import { TypeCustom } from "./type/TypeCustom";
import { FieldTypeText, PropTypeText } from "./type/TypeText";
import { TypeDropdown } from "./type/TypeDropdown";
import { FieldToggle } from "./type/TypeToggle";
import { SingleOption } from "./type/TypeSingleOption";
import { MultiOption } from "./type/TypeMultiOption"; import { MultiOption } from "./type/TypeMultiOption";
import { SingleOption } from "./type/TypeSingleOption";
import { FieldTypeText, PropTypeText } from "./type/TypeText";
const modify = { const modify = {
timeout: null as any, timeout: null as any,
@ -21,15 +14,13 @@ export const FieldInput: FC<{
fm: FMLocal; fm: FMLocal;
PassProp: any; PassProp: any;
child: any; child: any;
_item: any; _item: PrasiItem;
_meta: any;
_sync: (mitem: any, item: any) => void;
arg: FieldProp; arg: FieldProp;
}> = ({ field, fm, PassProp, child, _meta, _item, _sync, arg }) => { }> = ({ field, fm, arg }) => {
// return <></> // return <></>
const prefix = typeof field.prefix === "function" ? field.prefix() : typeof field.prefix === "string" ? field.prefix : null; const prefix = typeof field.prefix === "function" ? field.prefix() : typeof field.prefix === "string" ? field.prefix : null;
const suffix = typeof field.suffix === "function" ? field.suffix() : typeof field.suffix === "string" ? field.prefix : null; const suffix = typeof field.suffix === "function" ? field.suffix() : typeof field.suffix === "string" ? field.prefix : null;
const name = typeof field.name === 'function' ? field.name() : field.name; const name = field.name;
const errors = fm.error.get(name); const errors = fm.error.get(name);
let type_field = typeof arg.type === 'function' ? arg.type() : arg.type; // tipe field let type_field = typeof arg.type === 'function' ? arg.type() : arg.type; // tipe field

View File

@ -1,30 +0,0 @@
import { FMLocal, FieldInternal, FieldLocal, FieldProp } from "../typings";
export const fieldMapping: {
[K in FieldProp["type"]]: {
id: string;
props?:
| Record<string, any>
| ((
fm: FMLocal,
field: FieldInternal<K> & {
render: () => void;
}
) => Record<string, any>);
};
} = {
text: { id: "ca7ac237-8f22-4492-bb9d-4b715b1f5c25", props: { type: "text" } },
relation: {
id: "69263ca0-61a1-4899-ad5f-059ac12b94d1",
props: (fm, field) => {
const rel = fm.field_def[field.name];
if (rel) {
if (field.prop && !field.prop.type) {
return { type: rel.type };
}
}
return {};
},
},
switch: { id: ""},
};

View File

@ -1,172 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {mergeRegister} from '@lexical/utils';
import {
$getSelection,
$isRangeSelection,
CAN_REDO_COMMAND,
CAN_UNDO_COMMAND,
FORMAT_ELEMENT_COMMAND,
FORMAT_TEXT_COMMAND,
REDO_COMMAND,
SELECTION_CHANGE_COMMAND,
UNDO_COMMAND,
} from 'lexical';
import {useCallback, useEffect, useRef, useState} from 'react';
const LowPriority = 1;
function Divider() {
return <div className="divider" />;
}
export default function ToolbarPlugin() {
const [editor] = useLexicalComposerContext();
const toolbarRef = useRef(null);
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
const [isBold, setIsBold] = useState(false);
const [isItalic, setIsItalic] = useState(false);
const [isUnderline, setIsUnderline] = useState(false);
const [isStrikethrough, setIsStrikethrough] = useState(false);
const $updateToolbar = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
// Update text format
setIsBold(selection.hasFormat('bold'));
setIsItalic(selection.hasFormat('italic'));
setIsUnderline(selection.hasFormat('underline'));
setIsStrikethrough(selection.hasFormat('strikethrough'));
}
}, []);
useEffect(() => {
return mergeRegister(
editor.registerUpdateListener(({editorState}) => {
editorState.read(() => {
$updateToolbar();
});
}),
editor.registerCommand(
SELECTION_CHANGE_COMMAND,
(_payload, _newEditor) => {
$updateToolbar();
return false;
},
LowPriority,
),
editor.registerCommand(
CAN_UNDO_COMMAND,
(payload) => {
setCanUndo(payload);
return false;
},
LowPriority,
),
editor.registerCommand(
CAN_REDO_COMMAND,
(payload) => {
setCanRedo(payload);
return false;
},
LowPriority,
),
);
}, [editor, $updateToolbar]);
return (
<div className="toolbar" ref={toolbarRef}>
<button
disabled={!canUndo}
onClick={() => {
editor.dispatchCommand(UNDO_COMMAND, undefined);
}}
className="toolbar-item spaced"
aria-label="Undo">
<i className="format undo" />
</button>
<button
disabled={!canRedo}
onClick={() => {
editor.dispatchCommand(REDO_COMMAND, undefined);
}}
className="toolbar-item"
aria-label="Redo">
<i className="format redo" />
</button>
<Divider />
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
}}
className={'toolbar-item spaced ' + (isBold ? 'active' : '')}
aria-label="Format Bold">
<i className="format bold" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
}}
className={'toolbar-item spaced ' + (isItalic ? 'active' : '')}
aria-label="Format Italics">
<i className="format italic" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
}}
className={'toolbar-item spaced ' + (isUnderline ? 'active' : '')}
aria-label="Format Underline">
<i className="format underline" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
}}
className={'toolbar-item spaced ' + (isStrikethrough ? 'active' : '')}
aria-label="Format Strikethrough">
<i className="format strikethrough" />
</button>
<Divider />
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left');
}}
className="toolbar-item spaced"
aria-label="Left Align">
<i className="format left-align" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center');
}}
className="toolbar-item spaced"
aria-label="Center Align">
<i className="format center-align" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right');
}}
className="toolbar-item spaced"
aria-label="Right Align">
<i className="format right-align" />
</button>
<button
onClick={() => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'justify');
}}
className="toolbar-item"
aria-label="Justify Align">
<i className="format justify-align" />
</button>{' '}
</div>
);
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {TreeView} from '@lexical/react/LexicalTreeView';
export default function TreeViewPlugin(): JSX.Element {
const [editor] = useLexicalComposerContext();
return (
<TreeView
viewClassName="tree-view-output"
treeTypeButtonClassName="debug-treetype-button"
timeTravelPanelClassName="debug-timetravel-panel"
timeTravelButtonClassName="debug-timetravel-button"
timeTravelPanelSliderClassName="debug-timetravel-panel-slider"
timeTravelPanelButtonClassName="debug-timetravel-panel-button"
editor={editor}
/>
);
}

View File

@ -1,451 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
body {
margin: 0;
background: #eee;
font-family: system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular',
sans-serif;
font-weight: 500;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.other h2 {
font-size: 18px;
color: #444;
margin-bottom: 7px;
}
.other a {
color: #777;
text-decoration: underline;
font-size: 14px;
}
.other ul {
padding: 0;
margin: 0;
list-style-type: none;
}
.App {
font-family: sans-serif;
text-align: center;
}
h1 {
font-size: 24px;
color: #333;
}
.ltr {
text-align: left;
}
.rtl {
text-align: right;
}
.editor-container {
margin: 20px auto 20px auto;
border-radius: 2px;
max-width: 600px;
color: #000;
position: relative;
line-height: 20px;
font-weight: 400;
text-align: left;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.editor-inner {
background: #fff;
position: relative;
}
.editor-input {
min-height: 150px;
resize: none;
font-size: 15px;
caret-color: rgb(5, 5, 5);
position: relative;
tab-size: 1;
outline: 0;
padding: 15px 10px;
caret-color: #444;
}
.editor-placeholder {
color: #999;
overflow: hidden;
position: absolute;
text-overflow: ellipsis;
top: 15px;
left: 10px;
font-size: 15px;
user-select: none;
display: inline-block;
pointer-events: none;
}
.editor-text-bold {
font-weight: bold;
}
.editor-text-italic {
font-style: italic;
}
.editor-text-underline {
text-decoration: underline;
}
.editor-text-strikethrough {
text-decoration: line-through;
}
.editor-text-underlineStrikethrough {
text-decoration: underline line-through;
}
.editor-text-code {
background-color: rgb(240, 242, 245);
padding: 1px 0.25rem;
font-family: Menlo, Consolas, Monaco, monospace;
font-size: 94%;
}
.editor-link {
color: rgb(33, 111, 219);
text-decoration: none;
}
.tree-view-output {
display: block;
background: #222;
color: #fff;
padding: 5px;
font-size: 12px;
white-space: pre-wrap;
margin: 1px auto 10px auto;
max-height: 250px;
position: relative;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
overflow: auto;
line-height: 14px;
}
.editor-code {
background-color: rgb(240, 242, 245);
font-family: Menlo, Consolas, Monaco, monospace;
display: block;
padding: 8px 8px 8px 52px;
line-height: 1.53;
font-size: 13px;
margin: 0;
margin-top: 8px;
margin-bottom: 8px;
tab-size: 2;
/* white-space: pre; */
overflow-x: auto;
position: relative;
}
.editor-code:before {
content: attr(data-gutter);
position: absolute;
background-color: #eee;
left: 0;
top: 0;
border-right: 1px solid #ccc;
padding: 8px;
color: #777;
white-space: pre-wrap;
text-align: right;
min-width: 25px;
}
.editor-code:after {
content: attr(data-highlight-language);
top: 0;
right: 3px;
padding: 3px;
font-size: 10px;
text-transform: uppercase;
position: absolute;
color: rgba(0, 0, 0, 0.5);
}
.editor-tokenComment {
color: slategray;
}
.editor-tokenPunctuation {
color: #999;
}
.editor-tokenProperty {
color: #905;
}
.editor-tokenSelector {
color: #690;
}
.editor-tokenOperator {
color: #9a6e3a;
}
.editor-tokenAttr {
color: #07a;
}
.editor-tokenVariable {
color: #e90;
}
.editor-tokenFunction {
color: #dd4a68;
}
.editor-paragraph {
margin: 0;
margin-bottom: 8px;
position: relative;
}
.editor-paragraph:last-child {
margin-bottom: 0;
}
.editor-heading-h1 {
font-size: 24px;
color: rgb(5, 5, 5);
font-weight: 400;
margin: 0;
margin-bottom: 12px;
padding: 0;
}
.editor-heading-h2 {
font-size: 15px;
color: rgb(101, 103, 107);
font-weight: 700;
margin: 0;
margin-top: 10px;
padding: 0;
text-transform: uppercase;
}
.editor-quote {
margin: 0;
margin-left: 20px;
font-size: 15px;
color: rgb(101, 103, 107);
border-left-color: rgb(206, 208, 212);
border-left-width: 4px;
border-left-style: solid;
padding-left: 16px;
}
.editor-list-ol {
padding: 0;
margin: 0;
margin-left: 16px;
}
.editor-list-ul {
padding: 0;
margin: 0;
margin-left: 16px;
}
.editor-listitem {
margin: 8px 32px 8px 32px;
}
.editor-nested-listitem {
list-style-type: none;
}
pre::-webkit-scrollbar {
background: transparent;
width: 10px;
}
pre::-webkit-scrollbar-thumb {
background: #999;
}
.debug-timetravel-panel {
overflow: hidden;
padding: 0 0 10px 0;
margin: auto;
display: flex;
}
.debug-timetravel-panel-slider {
padding: 0;
flex: 8;
}
.debug-timetravel-panel-button {
padding: 0;
border: 0;
background: none;
flex: 1;
color: #fff;
font-size: 12px;
}
.debug-timetravel-panel-button:hover {
text-decoration: underline;
}
.debug-timetravel-button {
border: 0;
padding: 0;
font-size: 12px;
top: 10px;
right: 15px;
position: absolute;
background: none;
color: #fff;
}
.debug-timetravel-button:hover {
text-decoration: underline;
}
.toolbar {
display: flex;
margin-bottom: 1px;
background: #fff;
padding: 4px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
vertical-align: middle;
}
.toolbar button.toolbar-item {
border: 0;
display: flex;
background: none;
border-radius: 10px;
padding: 8px;
cursor: pointer;
vertical-align: middle;
}
.toolbar button.toolbar-item:disabled {
cursor: not-allowed;
}
.toolbar button.toolbar-item.spaced {
margin-right: 2px;
}
.toolbar button.toolbar-item i.format {
background-size: contain;
display: inline-block;
height: 18px;
width: 18px;
margin-top: 2px;
vertical-align: -0.25em;
display: flex;
opacity: 0.6;
}
.toolbar button.toolbar-item:disabled i.format {
opacity: 0.2;
}
.toolbar button.toolbar-item.active {
background-color: rgba(223, 232, 250, 0.3);
}
.toolbar button.toolbar-item.active i {
opacity: 1;
}
.toolbar .toolbar-item:hover:not([disabled]) {
background-color: #eee;
}
.toolbar .divider {
width: 1px;
background-color: #eee;
margin: 0 4px;
}
.toolbar .toolbar-item .text {
display: flex;
line-height: 20px;
width: 200px;
vertical-align: middle;
font-size: 14px;
color: #777;
text-overflow: ellipsis;
width: 70px;
overflow: hidden;
height: 20px;
text-align: left;
}
.toolbar .toolbar-item .icon {
display: flex;
width: 20px;
height: 20px;
user-select: none;
margin-right: 8px;
line-height: 16px;
background-size: contain;
}
i.undo {
background-image: url(icons/arrow-counterclockwise.svg);
}
i.redo {
background-image: url(icons/arrow-clockwise.svg);
}
i.bold {
background-image: url(icons/type-bold.svg);
}
i.italic {
background-image: url(icons/type-italic.svg);
}
i.underline {
background-image: url(icons/type-underline.svg);
}
i.strikethrough {
background-image: url(icons/type-strikethrough.svg);
}
i.left-align {
background-image: url(icons/text-left.svg);
}
i.center-align {
background-image: url(icons/text-center.svg);
}
i.right-align {
background-image: url(icons/text-right.svg);
}
i.justify-align {
background-image: url(icons/justify.svg);
}

View File

@ -1,8 +1,7 @@
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import get from "lodash.get"; import get from "lodash.get";
import { DMMF } from "../../../../../typings/runtime/library"; import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
export const FieldCheckbox: FC<{ export const FieldCheckbox: FC<{
field: FieldLocal; field: FieldLocal;

View File

@ -1,10 +1,6 @@
import { useLocal } from "@/utils/use-local";
import { FC } from "react"; import { FC } from "react";
import { FMLocal, FieldLocal } from "../../typings"; import { FMLocal, FieldLocal } from "../../typings";
import { useLocal } from "@/utils/use-local";
import parser from "any-date-parser";
import { format } from "date-fns";
import get from "lodash.get";
import { argv } from "bun";
import { PropTypeText } from "./TypeText"; import { PropTypeText } from "./TypeText";
export const FieldMoney: FC<{ export const FieldMoney: FC<{
field: FieldLocal; field: FieldLocal;

View File

@ -1,9 +1,9 @@
import { sortTree } from "@/comps/list/utils/sort-tree";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { FC, useEffect } from "react"; import { FC, useEffect } from "react";
import { FMLocal, FieldLocal } from "../../typings"; import { FMLocal, FieldLocal } from "../../typings";
import { OptionItem, RawDropdown } from "../raw/Dropdown"; import { OptionItem, RawDropdown } from "../raw/Dropdown";
import { FieldLoading } from "../raw/FieldLoading"; import { FieldLoading } from "../raw/FieldLoading";
import { sortTree } from "@/comps/list/utils/sort-tree";
export type PropTypeRelation = { export type PropTypeRelation = {
type: "has-one" | "has-many"; type: "has-one" | "has-many";
@ -38,7 +38,7 @@ const HasMany: FC<{
many: [] as { value: string; label: string }[], many: [] as { value: string; label: string }[],
pk: "", pk: "",
}); });
const name = typeof field.name === 'string' ? field.name : field.name(); const name = field.name;
const value = fm.data[name]; const value = fm.data[name];
field.input = input; field.input = input;
field.prop = prop; field.prop = prop;
@ -100,7 +100,7 @@ const HasOne: FC<{
list: null as null | any[], list: null as null | any[],
pk: "", pk: "",
}); });
const name = typeof field.name === 'string' ? field.name : field.name(); const name = field.name;
const value = fm.data[name]; const value = fm.data[name];
field.input = input; field.input = input;
field.prop = prop; field.prop = prop;

View File

@ -1,14 +1,15 @@
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import Quill from "quill"; import Quill from "quill";
import "quill/dist/quill.snow.css"; // Import CSS untuk tema Quill import "quill/dist/quill.snow.css"; // Import CSS untuk tema Quill
import get from "lodash.get"; import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { PropTypeText } from "./TypeText";
export const FieldRichText: FC<{ export const FieldRichText: FC<{
field: FieldLocal; field: FieldLocal;
fm: FMLocal; fm: FMLocal;
arg: FieldProp; prop: PropTypeText;
}> = ({ field, fm, arg }) => { }> = ({ field, fm, prop }) => {
const local = useLocal({ const local = useLocal({
ref: null as any, ref: null as any,
}); });

View File

@ -1,20 +1,6 @@
import { Label } from "@/comps/ui/label";
import { Switch } from "@/comps/ui/switch";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/comps/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
} from "@/comps/ui/form";
import { FC } from "react";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { FC } from "react";
export type PropTypeSwitch = {}; export type PropTypeSwitch = {};
export const FieldTypeSwitch: FC<{ export const FieldTypeSwitch: FC<{

View File

@ -1,7 +1,6 @@
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import get from "lodash.get"; import { FC } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
export const FieldTag: FC<{ export const FieldTag: FC<{
field: FieldLocal; field: FieldLocal;

View File

@ -1,10 +1,7 @@
import { useLocal } from "@/utils/use-local";
import get from "lodash.get";
import { FC } from "react"; import { FC } from "react";
import { FMLocal, FieldLocal } from "../../typings"; import { FMLocal, FieldLocal } from "../../typings";
import { useLocal } from "@/utils/use-local";
import parser from "any-date-parser";
import { format } from "date-fns";
import get from "lodash.get";
import { argv } from "bun";
import { PropTypeText } from "./TypeText"; import { PropTypeText } from "./TypeText";
export const FieldUpload: FC<{ export const FieldUpload: FC<{
field: FieldLocal; field: FieldLocal;

View File

@ -23,8 +23,8 @@ export type FMProps = {
type FieldType = "-" | "relation" | "switch" | "input" | "single-option" | "multi-option"; type FieldType = "-" | "relation" | "switch" | "input" | "single-option" | "multi-option";
export type FieldProp = { export type FieldProp = {
name: string | (() => string); name: string;
label: string | (() => string); label: string;
desc?: string; desc?: string;
props?: any; props?: any;
fm: FMLocal; fm: FMLocal;
@ -60,9 +60,9 @@ export type FMInternal = {
error: { error: {
readonly object: Record<string, string>; readonly object: Record<string, string>;
readonly list: { name: string; error: string[] }[]; readonly list: { name: string; error: string[] }[];
set: (name: string | number | symbol, error: string[]) => void; set: (name: string, error: string[]) => void;
get: (name: string | number | symbol) => string[]; get: (name: string) => string[];
clear: (name?: string | number | symbol) => void; clear: (name?: string) => void;
}; };
internal: { internal: {
reload: { reload: {

View File

@ -1,50 +0,0 @@
import { newField } from "@/gen/gen_form/new_field";
import { FMLocal, FieldLocal } from "../typings";
import get from "lodash.get";
import { fieldMapping } from "../field/mapping";
import { createItem } from "@/gen/utils";
export const genFieldMitem = (arg: {
_meta: any;
_item: any;
_sync: any;
fm: FMLocal;
field: FieldLocal;
}) => {
const { _meta, _item, _sync, fm, field } = arg;
const m = _meta[_item.id];
if (m) {
const mitem = m.mitem;
if (mitem) {
const childs = mitem
.get("component")
?.get("props")
?.get("child")
?.get("content")
?.get("childs");
let component = fieldMapping[field.type as "text"];
if (!component) {
component = fieldMapping["text"];
}
if (component) {
const item = createItem({
component: component as any,
});
_sync(childs, [...childs.toJSON(), item]);
}
}
}
};
export const updateFieldMItem = (_meta: any, _item: any, _sync: any) => {
const m = _meta[_item.id];
if (m) {
const mitem = m.mitem;
if (mitem) {
_sync(mitem, _item);
}
}
};

View File

@ -2,7 +2,10 @@ import { useLocal } from "@/utils/use-local";
import { useEffect } from "react"; import { useEffect } from "react";
import { FieldInternal, FieldProp } from "../typings"; import { FieldInternal, FieldProp } from "../typings";
export const useField = (arg: FieldProp) => { export const useField = (arg: Omit<FieldProp, 'name' | 'label'> & {
name: string | (() => string);
label: string | (() => string)
}) => {
const field = useLocal<FieldInternal<typeof arg.type>>({ const field = useLocal<FieldInternal<typeof arg.type>>({
status: "init", status: "init",
Child: () => { Child: () => {

View File

@ -40,15 +40,17 @@ export const MasterDetail: FC<{
let isGenerate = false as Boolean; let isGenerate = false as Boolean;
try { try {
if (!get(w.md_internal, _item.id)) w.md_internal = { if (!get(w.md_internal, _item.id))
w.md_internal = {
...w.md_internal, ...w.md_internal,
[_item.id]: _item [_item.id]: _item,
}; };
isGenerate = false; isGenerate = false;
if (w.generating_prasi_md["master_detail"]) { if (w.generating_prasi_md["master_detail"]) {
isGenerate = true; isGenerate = true;
} }
} catch (ex) { } } catch (ex) {}
const _ref = useRef({ PassProp, child }); const _ref = useRef({ PassProp, child });
const md = useLocal<MDLocalInternal>({ const md = useLocal<MDLocalInternal>({
name, name,
@ -80,14 +82,13 @@ export const MasterDetail: FC<{
masterDetailApplyParams(md); masterDetailApplyParams(md);
}, },
}, },
master: { internal: null, render() { } }, master: { internal: null, render() {} },
panel: { panel: {
size: 25, size: 25,
min_size: 0, min_size: 0,
}, },
}); });
const local = useLocal({ init: false }); const local = useLocal({ init: false });
if (isEditor) { if (isEditor) {
md.props.mode = mode; md.props.mode = mode;
@ -116,7 +117,7 @@ export const MasterDetail: FC<{
return ( return (
<div <div
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.props.show_head === "always" && ( {md.props.show_head === "always" && (
@ -127,7 +128,7 @@ export const MasterDetail: FC<{
{md.props.mode === "h-split" && <ModeHSplit md={md} mdr={_ref.current} />} {md.props.mode === "h-split" && <ModeHSplit md={md} mdr={_ref.current} />}
</div> </div>
); );
}; };
const ModeFull: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => { const ModeFull: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
if (should_show_tab(md)) { if (should_show_tab(md)) {

View File

@ -127,4 +127,8 @@ export const MasterDetailType = `const md = {
md?: md; md?: md;
} }
>; >;
panel: {
size: number;
min_size: number;
};
};`; };`;

View File

@ -1,177 +0,0 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form";
import { cn } from "@/utils";
import { Label } from "@/comps/ui/label";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("c-space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && "c-text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("c-text-sm c-text-muted-foreground", className)}
{...props}
/>
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("c-text-sm c-font-medium c-text-destructive", className)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = "FormMessage";
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};

View File

@ -40,6 +40,8 @@ const load_single = async (table: string) => {
return single[table]; return single[table];
}; };
export const gen_prop_fields = async (gen_table: string) => { export const gen_prop_fields = async (gen_table: string) => {
if (typeof db === "undefined") return ["- No Database -"];
const path = window.location.pathname; const path = window.location.pathname;
let id_site = null; let id_site = null;
try { try {

View File

@ -1,6 +1,8 @@
const cache: any = []; const cache: any = [];
export const gen_props_table = async () => { export const gen_props_table = async () => {
if (typeof db === "undefined") return ["- No Database -"];
if (cache.length > 0) return cache; if (cache.length > 0) return cache;
const tables = await db._schema.tables(); const tables = await db._schema.tables();
if (!Array.isArray(tables)) { if (!Array.isArray(tables)) {