fix
This commit is contained in:
parent
c7965e81b8
commit
3d470e3f3e
|
|
@ -13,7 +13,6 @@ const editorFormWidth = {} as Record<string, { w: number; f: any }>;
|
||||||
export { FMLocal } from "./typings";
|
export { FMLocal } from "./typings";
|
||||||
|
|
||||||
export const Form: FC<FMProps> = (props) => {
|
export const Form: FC<FMProps> = (props) => {
|
||||||
|
|
||||||
const { PassProp, body } = props;
|
const { PassProp, body } = props;
|
||||||
const fm = useLocal<FMInternal>({
|
const fm = useLocal<FMInternal>({
|
||||||
data: editorFormData[props.item.id]
|
data: editorFormData[props.item.id]
|
||||||
|
|
@ -121,6 +120,7 @@ export const Form: FC<FMProps> = (props) => {
|
||||||
}, 100);
|
}, 100);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
||||||
import { Label } from "../field/Label";
|
import { Label } from "../field/Label";
|
||||||
import { FieldLoading } from "../field/raw/FieldLoading";
|
import { FieldLoading } from "../../ui/field-loading";
|
||||||
|
|
||||||
export const BaseField = (prop: {
|
export const BaseField = (prop: {
|
||||||
field: FieldLocal;
|
field: FieldLocal;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { FC, isValidElement } from "react";
|
import { FC, isValidElement } from "react";
|
||||||
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
||||||
import { FieldLoading } from "./raw/FieldLoading";
|
import { FieldLoading } from "../../ui/field-loading";
|
||||||
import { MultiOption } from "./type/TypeMultiOption";
|
import { MultiOption } from "./type/TypeMultiOption";
|
||||||
import { SingleOption } from "./type/TypeSingleOption";
|
import { SingleOption } from "./type/TypeSingleOption";
|
||||||
import { FieldTypeText, PropTypeText } from "./type/TypeText";
|
import { FieldTypeText, PropTypeText } from "./type/TypeText";
|
||||||
|
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
import { Popover } from "@/comps/custom/Popover";
|
|
||||||
import { useLocal } from "@/utils/use-local";
|
|
||||||
import { ChevronDown } from "lucide-react";
|
|
||||||
import { FC, ReactNode } from "react";
|
|
||||||
|
|
||||||
export type OptionItem = { value: any; label: string; el?: ReactNode };
|
|
||||||
|
|
||||||
export const RawDropdown: FC<{
|
|
||||||
options: OptionItem[];
|
|
||||||
className?: string;
|
|
||||||
value: string;
|
|
||||||
onFocus?: () => void;
|
|
||||||
onBlur?: () => void;
|
|
||||||
onChange?: (value: string) => void;
|
|
||||||
disabled?: boolean;
|
|
||||||
}> = ({ value, options, className, onFocus, onBlur, onChange, disabled }) => {
|
|
||||||
const local = useLocal({
|
|
||||||
open: false,
|
|
||||||
input: {
|
|
||||||
value: "",
|
|
||||||
el: null as any,
|
|
||||||
},
|
|
||||||
filter: "",
|
|
||||||
width: 0,
|
|
||||||
selected: undefined as undefined | OptionItem,
|
|
||||||
});
|
|
||||||
|
|
||||||
let filtered = options;
|
|
||||||
|
|
||||||
if (local.filter) {
|
|
||||||
filtered = options.filter((e: any) => {
|
|
||||||
if (typeof e === "string") {
|
|
||||||
if (e.toLowerCase().includes(local.filter)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof e.label === "string" &&
|
|
||||||
e.label.toLowerCase().includes(local.filter)
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
local.selected = options.find((e) => e.value === value);
|
|
||||||
|
|
||||||
if (disabled || isEditor) {
|
|
||||||
local.open = false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
open={disabled ? false : local.open}
|
|
||||||
onOpenChange={() => {
|
|
||||||
local.open = false;
|
|
||||||
local.render();
|
|
||||||
}}
|
|
||||||
arrow={false}
|
|
||||||
className={cx("c-rounded-sm c-bg-white")}
|
|
||||||
content={
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
"c-text-sm",
|
|
||||||
css`
|
|
||||||
width: ${local.width || 100}px;
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
{filtered.map((item, idx) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
tabIndex={0}
|
|
||||||
key={item.value + "_" + idx}
|
|
||||||
className={cx(
|
|
||||||
"c-px-3 c-py-1 cursor-pointer option-item",
|
|
||||||
item.value === value
|
|
||||||
? "c-bg-blue-600 c-text-white"
|
|
||||||
: "hover:c-bg-blue-50",
|
|
||||||
idx > 0 && "c-border-t",
|
|
||||||
idx === 0 && "c-rounded-t-sm",
|
|
||||||
idx === filtered.length - 1 && "c-rounded-b-sm"
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
local.open = false;
|
|
||||||
local.render();
|
|
||||||
if (onChange) onChange(item.value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.el || item.label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
"c-relative",
|
|
||||||
className,
|
|
||||||
css`
|
|
||||||
cursor: pointer !important;
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
tabIndex={0}
|
|
||||||
onFocus={() => {
|
|
||||||
local.open = true;
|
|
||||||
if (local.selected) local.input.value = local.selected.label;
|
|
||||||
local.filter = "";
|
|
||||||
local.render();
|
|
||||||
setTimeout(() => {
|
|
||||||
local.input.el?.focus();
|
|
||||||
local.input.el?.select();
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
ref={(el) => {
|
|
||||||
if (local.width === 0 && el) {
|
|
||||||
const box = el.getBoundingClientRect();
|
|
||||||
if (box && box.width) {
|
|
||||||
local.width = box.width;
|
|
||||||
local.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="c-w-full c-h-full c-relative">
|
|
||||||
{!isEditor && (
|
|
||||||
<input
|
|
||||||
spellCheck={false}
|
|
||||||
value={local.open ? local.input.value : ""}
|
|
||||||
className={cx(
|
|
||||||
"c-absolute c-inset-0 c-w-full c-h-full c-outline-none c-p-0",
|
|
||||||
disabled
|
|
||||||
? "c-invisible"
|
|
||||||
: local.open
|
|
||||||
? "c-cursor-pointer"
|
|
||||||
: "c-pointer-events-none c-invisible"
|
|
||||||
)}
|
|
||||||
onChange={(e) => {
|
|
||||||
local.input.value = e.currentTarget.value;
|
|
||||||
local.filter = local.input.value.toLowerCase();
|
|
||||||
local.render();
|
|
||||||
}}
|
|
||||||
ref={(el) => {
|
|
||||||
local.input.el = el;
|
|
||||||
}}
|
|
||||||
type="text"
|
|
||||||
onFocus={onFocus}
|
|
||||||
onBlur={onBlur}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!local.open && local.selected && (
|
|
||||||
<div className="c-absolute c-inset-0 c-z-10 c-w-full c-h-full c-text-sm c-flex c-items-center">
|
|
||||||
{local.selected.el || local.selected.label}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
"c-absolute c-pointer-events-none c-z-10 c-inset-0 c-left-auto c-flex c-items-center ",
|
|
||||||
"c-bg-white c-justify-center c-w-6 c-mr-1 c-my-2",
|
|
||||||
disabled && "c-hidden"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ChevronDown size={14} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -10,21 +10,22 @@ export const FieldButton: FC<{
|
||||||
}> = ({ field, fm, arg }) => {
|
}> = ({ field, fm, arg }) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
list: [] as any[],
|
list: [] as any[],
|
||||||
value: [] as any[],
|
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const callback = (res: any[]) => {
|
const callback = (res: any[]) => {
|
||||||
local.list = res;
|
local.list = res;
|
||||||
if (Array.isArray(res)) {
|
|
||||||
local.value = res.map((e) => get(e, arg.pk));
|
|
||||||
}
|
|
||||||
local.render();
|
local.render();
|
||||||
};
|
};
|
||||||
const res = arg.on_load();
|
const res = arg.on_load();
|
||||||
if (res instanceof Promise) res.then(callback);
|
if (res instanceof Promise) res.then(callback);
|
||||||
else callback(res);
|
else callback(res);
|
||||||
}, []);
|
}, []);
|
||||||
let value: any = fm.data[field.name];
|
let value = arg.opt_get_value({
|
||||||
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
|
|
||||||
if (arg.type === "multi-option") {
|
if (arg.type === "multi-option") {
|
||||||
value = fm.data[field.name] || [];
|
value = fm.data[field.name] || [];
|
||||||
|
|
@ -42,32 +43,45 @@ export const FieldButton: FC<{
|
||||||
{local.list.map((item) => {
|
{local.list.map((item) => {
|
||||||
let isChecked = false;
|
let isChecked = false;
|
||||||
try {
|
try {
|
||||||
isChecked = value.some((e: any) => e[arg.pk] === item[arg.pk]);
|
isChecked = value.some((e: any) => e === item[arg.pk]);
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!Array.isArray(fm.data[field.name]))
|
let selected = Array.isArray(value)
|
||||||
fm.data[field.name] = [];
|
? value.map((row) => {
|
||||||
|
return local.list.find((e) => e.value === row);
|
||||||
|
})
|
||||||
|
: [];
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
fm.data[field.name] = fm.data[field.name].filter(
|
selected = selected.filter(
|
||||||
(e: any) => e[arg.pk] !== item[arg.pk]
|
(e: any) => e.value !== item.value
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
fm.data[field.name].push(item);
|
selected.push(item);
|
||||||
}
|
}
|
||||||
fm.render();
|
|
||||||
|
arg.opt_set_value({
|
||||||
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
selected: selected.map((e) => e.value),
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
draggable="true"
|
draggable="true"
|
||||||
role="button"
|
role="button"
|
||||||
title="Hover chip"
|
title="Hover chip"
|
||||||
className={cx(
|
className={cx(
|
||||||
isChecked ? "c-bg-gray-200" : "c-border c-border-gray-500",
|
isChecked
|
||||||
" c-text-gray-700 c-h-8 c-px-3 c-w-max c-flex c-gap-2 c-items-center c-rounded-full hover:c-bg-gray-300 hover:c-bg-opacity-75 "
|
? "c-border c-border-blue-500 c-bg-blue-500 c-text-white"
|
||||||
|
: "c-border c-border-gray-500 hover:c-bg-gray-300 ",
|
||||||
|
"c-text-sm c-text-gray-700 c-h-8 c-px-3 c-w-max c-flex c-gap-2 c-items-center c-rounded-full hover:c-bg-opacity-75 "
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="block text-sm font-medium">
|
<span className="block text-sm font-medium">
|
||||||
{arg.on_row(item)}
|
{arg.opt_get_label(item)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -84,7 +98,7 @@ export const FieldButton: FC<{
|
||||||
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
`c-grid c-grid-cols- c-flex-grow c-gap-2 c-rounded-md c-bg-gray-200 c-p-0.5`,
|
`c-grid c-grid-cols- c-flex-grow c-gap-2 c-rounded-md c-bg-gray-200 c-p-1`,
|
||||||
css`
|
css`
|
||||||
grid-template-columns: repeat(
|
grid-template-columns: repeat(
|
||||||
${local.list.length},
|
${local.list.length},
|
||||||
|
|
@ -99,15 +113,20 @@ export const FieldButton: FC<{
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
fm.data[field.name] = get(e, arg.pk);
|
arg.opt_set_value({
|
||||||
fm.render();
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
selected: [e.value],
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
className={cx(
|
className={cx(
|
||||||
`${checked ? "c-bg-blue-500 c-text-white" : ""} `,
|
`${checked ? "c-bg-blue-500 c-text-white" : ""} `,
|
||||||
"c-block c-cursor-pointer c-select-none c-rounded-md c-p-1 c-text-center peer-checked: peer-checked:c-font-bold"
|
"c-text-sm c-block c-cursor-pointer c-select-none c-rounded-md c-p-1 c-text-center peer-checked: peer-checked:c-font-bold"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{arg.on_row(e)}
|
{arg.opt_get_label(e)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,24 @@ export const FieldCheckbox: FC<{
|
||||||
}> = ({ field, fm, arg }) => {
|
}> = ({ field, fm, arg }) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
list: [] as any[],
|
list: [] as any[],
|
||||||
value: [] as any[],
|
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const callback = (res: any[]) => {
|
const callback = (res: any[]) => {
|
||||||
local.list = res;
|
local.list = res;
|
||||||
if (Array.isArray(res)) {
|
|
||||||
local.value = res.map((e) => get(e, arg.pk));
|
|
||||||
}
|
|
||||||
local.render();
|
local.render();
|
||||||
};
|
};
|
||||||
const res = arg.on_load();
|
const res = arg.on_load();
|
||||||
if (res instanceof Promise) res.then(callback);
|
if (res instanceof Promise) res.then(callback);
|
||||||
else callback(res);
|
else callback(res);
|
||||||
}, []);
|
}, []);
|
||||||
let value: any = Array.isArray(fm.data[field.name]) ?fm.data[field.name] : [];
|
|
||||||
console.log({value})
|
let value = arg.opt_get_value({
|
||||||
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||||
|
|
@ -33,25 +35,32 @@ export const FieldCheckbox: FC<{
|
||||||
{local.list.map((item) => {
|
{local.list.map((item) => {
|
||||||
let isChecked = false;
|
let isChecked = false;
|
||||||
try {
|
try {
|
||||||
isChecked = value.some((e: any) => e[arg.pk] === item[arg.pk]);
|
isChecked = value.some((e: any) => e === item[arg.pk]);
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
console.log(item[arg.pk], isChecked)
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log(item);
|
let selected = Array.isArray(value)
|
||||||
if (!Array.isArray(fm.data[field.name]))
|
? value.map((row) => {
|
||||||
fm.data[field.name] = [];
|
return local.list.find((e) => e.value === row);
|
||||||
console.log(isChecked);
|
})
|
||||||
|
: [];
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
fm.data[field.name] = fm.data[field.name].filter(
|
selected = selected.filter(
|
||||||
(e: any) => e[arg.pk] !== item[arg.pk]
|
(e: any) => e.value !== item.value
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
fm.data[field.name].push(item);
|
selected.push(item);
|
||||||
}
|
}
|
||||||
fm.render();
|
|
||||||
console.log({data: fm.data})
|
arg.opt_set_value({
|
||||||
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
selected: selected.map((e) => e.value),
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
className="c-flex c-flex-row c-space-x-1 cursor-pointer c-items-center rounded-full p-0.5"
|
className="c-flex c-flex-row c-space-x-1 cursor-pointer c-items-center rounded-full p-0.5"
|
||||||
>
|
>
|
||||||
|
|
@ -81,7 +90,7 @@ export const FieldCheckbox: FC<{
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
<div className="">{arg.on_row(item)}</div>
|
<div className="">{arg.opt_get_label(item)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,97 @@
|
||||||
import { FC, useEffect } from "react";
|
|
||||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import { OptionItem, RawDropdown } from "../raw/Dropdown";
|
import { FC, useEffect } from "react";
|
||||||
import { FieldLoading } from "../raw/FieldLoading";
|
import { Typeahead } from "../../../../..";
|
||||||
import get from "lodash.get";
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { FieldLoading } from "lib/comps/ui/field-loading";
|
||||||
|
|
||||||
export const TypeDropdown: FC<{
|
export const TypeDropdown: FC<{
|
||||||
field: FieldLocal;
|
field: FieldLocal;
|
||||||
fm: FMLocal;
|
fm: FMLocal;
|
||||||
arg: FieldProp;
|
arg: FieldProp;
|
||||||
}> = ({ field, fm, arg }) => {
|
}> = ({ field, fm, arg }) => {
|
||||||
const input = useLocal({
|
const local = useLocal({
|
||||||
list: null as null | any[],
|
loaded: false,
|
||||||
pk: "",
|
options: [],
|
||||||
});
|
});
|
||||||
const value = fm.data[field.name];
|
let value = arg.opt_get_value({
|
||||||
field.input = input;
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
options: local.options,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
|
console.log({ value });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEditor && input.list === null) {
|
if (typeof arg.on_load === "function") {
|
||||||
field.status = "loading";
|
const options = arg.on_load({ mode: "query" });
|
||||||
input.pk = arg.pk;
|
console.log("Masuk");
|
||||||
field.render();
|
// console.log(options)
|
||||||
const callback = (arg: any[]) => {
|
if (options instanceof Promise) {
|
||||||
input.list = arg;
|
options.then((res) => {
|
||||||
field.status = "ready";
|
console.log({ res });
|
||||||
input.render();
|
local.options = res;
|
||||||
};
|
local.loaded = true;
|
||||||
const res = arg.on_load();
|
local.render();
|
||||||
if (res instanceof Promise) res.then(callback);
|
});
|
||||||
else callback(res);
|
} else {
|
||||||
|
local.options = options;
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let list: OptionItem[] = [];
|
if (!local.loaded) return <FieldLoading />;
|
||||||
if (input.list && input.list.length) {
|
|
||||||
input.list.map((e: any) => {
|
if (field.type === "single-option")
|
||||||
let id = null;
|
|
||||||
try {
|
|
||||||
id = e[arg.pk];
|
|
||||||
} catch (ex: any) {
|
|
||||||
console.error(
|
|
||||||
"Error: PK Invalid atau tidak ditemukan, cek lagi keys yang ingin dijadikan value"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
list.push({
|
|
||||||
value: id,
|
|
||||||
label: arg.on_row(e),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let selected = null;
|
|
||||||
if (value && typeof value === "object") {
|
|
||||||
if (input.pk) selected = value[input.pk];
|
|
||||||
} else {
|
|
||||||
selected = value;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Typeahead
|
||||||
{field.status === "loading" ? (
|
value={value}
|
||||||
<FieldLoading />
|
onSelect={({ search, item }) => {
|
||||||
) : (
|
if (item) {
|
||||||
<RawDropdown
|
arg.opt_set_value({
|
||||||
options={list}
|
fm,
|
||||||
value={selected}
|
name: field.name,
|
||||||
onChange={(val) => {
|
type: field.type,
|
||||||
if (val === null) {
|
options: local.options,
|
||||||
fm.data[field.name] = null;
|
selected: [item.value],
|
||||||
fm.render();
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (input.list && input.pk) {
|
|
||||||
for (const item of input.list) {
|
|
||||||
if (item[input.pk] === val) {
|
|
||||||
fm.data[field.name] = val;
|
|
||||||
fm.render();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return item?.value || search;
|
||||||
}}
|
}}
|
||||||
className="c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full c-h-full"
|
allowNew={false}
|
||||||
disabled={field.disabled}
|
autoPopupWidth={true}
|
||||||
onFocus={() => {
|
focusOpen={true}
|
||||||
field.focused = true;
|
mode={"single"}
|
||||||
field.render();
|
placeholder={arg.placeholder}
|
||||||
}}
|
options={() => {
|
||||||
onBlur={() => {
|
return local.options;
|
||||||
field.focused = false;
|
}}
|
||||||
field.render();
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typeahead
|
||||||
|
value={value}
|
||||||
|
onSelect={({ search, item }) => {
|
||||||
|
return item?.value || search;
|
||||||
|
}}
|
||||||
|
onChange={(values) => {
|
||||||
|
arg.opt_set_value({
|
||||||
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
type: field.type,
|
||||||
|
options: local.options,
|
||||||
|
selected: values,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
allowNew={false}
|
||||||
|
autoPopupWidth={true}
|
||||||
|
focusOpen={true}
|
||||||
|
mode={"multi"}
|
||||||
|
placeholder={arg.placeholder}
|
||||||
|
options={() => {
|
||||||
|
return local.options;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
import { FC, useEffect } from "react";
|
import { FC } from "react";
|
||||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
import { useLocal } from "@/utils/use-local";
|
|
||||||
import get from "lodash.get";
|
|
||||||
import { TypeDropdown } from "./TypeDropdown";
|
|
||||||
import { FieldToggle } from "./TypeToggle";
|
|
||||||
import { FieldButton } from "./TypeButton";
|
import { FieldButton } from "./TypeButton";
|
||||||
import { FieldRadio } from "./TypeRadio";
|
|
||||||
import { FieldCheckbox } from "./TypeCheckbox";
|
import { FieldCheckbox } from "./TypeCheckbox";
|
||||||
|
import { TypeDropdown } from "./TypeDropdown";
|
||||||
import { FieldTag } from "./TypeTag";
|
import { FieldTag } from "./TypeTag";
|
||||||
|
|
||||||
export const MultiOption: FC<{
|
export const MultiOption: FC<{
|
||||||
|
|
@ -16,7 +12,9 @@ export const MultiOption: FC<{
|
||||||
}> = ({ field, fm, arg }) => {
|
}> = ({ field, fm, arg }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{arg.sub_type === "checkbox" ? (
|
{arg.sub_type === "typeahead" ? (
|
||||||
|
<TypeDropdown field={field} fm={fm} arg={arg}/>
|
||||||
|
):arg.sub_type === "checkbox" ? (
|
||||||
<FieldCheckbox field={field} fm={fm} arg={arg}/>
|
<FieldCheckbox field={field} fm={fm} arg={arg}/>
|
||||||
): arg.sub_type === "button" ? (
|
): arg.sub_type === "button" ? (
|
||||||
<FieldButton arg={arg} field={field} fm={fm} />
|
<FieldButton arg={arg} field={field} fm={fm} />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +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 { FC, useEffect } from "react";
|
||||||
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
|
||||||
export const FieldRadio: FC<{
|
export const FieldRadio: FC<{
|
||||||
field: FieldLocal;
|
field: FieldLocal;
|
||||||
|
|
@ -24,9 +24,13 @@ export const FieldRadio: FC<{
|
||||||
if (res instanceof Promise) res.then(callback);
|
if (res instanceof Promise) res.then(callback);
|
||||||
else callback(res);
|
else callback(res);
|
||||||
}, []);
|
}, []);
|
||||||
let listValue = [];
|
|
||||||
let value: any = fm.data[field.name];
|
let value = arg.opt_get_value({
|
||||||
let checked = local.value.indexOf(value) > 0 ? true : false;
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||||
|
|
@ -36,38 +40,24 @@ export const FieldRadio: FC<{
|
||||||
<div
|
<div
|
||||||
className="flex items-center mb-4"
|
className="flex items-center mb-4"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
fm.data[field.name] = get(e, arg.pk);
|
arg.opt_set_value({
|
||||||
fm.render();
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
selected: [e.value],
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id="country-option-1"
|
|
||||||
type="radio"
|
type="radio"
|
||||||
name="countries"
|
|
||||||
value="USA"
|
|
||||||
className="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300"
|
className="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300"
|
||||||
aria-labelledby="country-option-1"
|
aria-labelledby="country-option-1"
|
||||||
aria-describedby="country-option-1"
|
aria-describedby="country-option-1"
|
||||||
checked={get(e, arg.pk) === value}
|
checked={get(e, arg.pk) === value}
|
||||||
/>
|
/>
|
||||||
<label className="text-sm font-medium text-gray-900 ml-2 block">
|
<label className="text-sm font-medium text-gray-900 ml-2 block">
|
||||||
{arg.on_row(e)}
|
{arg.opt_get_label(e)}
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
className={cx(
|
|
||||||
`${
|
|
||||||
get(e, arg.pk) === value
|
|
||||||
? "c-bg-blue-500 c-text-white"
|
|
||||||
: ""
|
|
||||||
} `,
|
|
||||||
"c-block c-cursor-pointer c-select-none c-rounded-md c-p-1 c-text-center peer-checked: peer-checked:c-font-bold"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{arg.on_row(e)}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ 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 "../../../ui/field-loading";
|
||||||
|
|
||||||
export type PropTypeRelation = {
|
export type PropTypeRelation = {
|
||||||
type: "has-one" | "has-many";
|
type: "has-one" | "has-many";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
|
import { Typeahead } from "../../../../..";
|
||||||
|
|
||||||
export const FieldTag: FC<{
|
export const FieldTag: FC<{
|
||||||
field: FieldLocal;
|
field: FieldLocal;
|
||||||
|
|
@ -13,95 +14,20 @@ export const FieldTag: FC<{
|
||||||
value: null as any,
|
value: null as any,
|
||||||
});
|
});
|
||||||
let value: any = fm.data[field.name];
|
let value: any = fm.data[field.name];
|
||||||
let tags: Array<string> = typeof value === "string" ? value.split(",") : [];
|
|
||||||
if(isEditor){
|
|
||||||
tags = ["sample","sample"]
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
|
<Typeahead
|
||||||
<div
|
value={value}
|
||||||
className={cx(
|
onSelect={({ search, item }) => {
|
||||||
"c-px-2 c-flex c-flex-row c-items-center c-flex-wrap c-flex-grow c-gap-1 c-m-1"
|
return item?.value || search;
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
if (local.ref) {
|
|
||||||
local.ref.focus();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
allowNew
|
||||||
{tags.map((item) => {
|
focusOpen={false}
|
||||||
return (
|
placeholder={arg.placeholder}
|
||||||
<div className="c-cursor-text c-flex-row c-flex c-items-center c-text-xs c-font-medium c-rounded c-border c-border-black">
|
options={async () => {
|
||||||
<span className="c-flex-grow c-px-2.5 c-py-0.5">{item}</span>
|
if (typeof arg.on_load === "function") return await arg.on_load();
|
||||||
<svg
|
return [];
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
className="c-px-1 c-border-l c-border-black c-cursor-pointer "
|
|
||||||
viewBox="0 0 40 40"
|
|
||||||
onClick={() => {
|
|
||||||
// delete tag, pakai filter
|
|
||||||
let tag: Array<string> = tags.filter((e) => e !== item) || [];
|
|
||||||
// jadiin value string
|
|
||||||
let value = tags.join(",");
|
|
||||||
fm.data[field.name] = value;
|
|
||||||
fm.render();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M21.499 19.994L32.755 8.727a1.064 1.064 0 0 0-.001-1.502c-.398-.396-1.099-.398-1.501.002L20 18.494L8.743 7.224c-.4-.395-1.101-.393-1.499.002a1.05 1.05 0 0 0-.309.751c0 .284.11.55.309.747L18.5 19.993L7.245 31.263a1.064 1.064 0 0 0 .003 1.503c.193.191.466.301.748.301h.006c.283-.001.556-.112.745-.305L20 21.495l11.257 11.27c.199.198.465.308.747.308a1.058 1.058 0 0 0 1.061-1.061c0-.283-.11-.55-.31-.747z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<input
|
|
||||||
ref={(el) => (local.ref = el)}
|
|
||||||
type={"text"}
|
|
||||||
value={local.value}
|
|
||||||
onClick={() => {}}
|
|
||||||
onChange={(ev) => {
|
|
||||||
local.value = ev.currentTarget.value;
|
|
||||||
local.render();
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === "Enter") {
|
|
||||||
event.preventDefault();
|
|
||||||
// detect string kosong
|
|
||||||
if (local.value !== "" && local.value) {
|
|
||||||
// jadiin array atau split
|
|
||||||
let tag: Array<string> = local.value.split(",") || [];
|
|
||||||
// filter tag dari value gk boleh sama
|
|
||||||
tag = tag.filter((e) => !tags.includes(e));
|
|
||||||
// concat
|
|
||||||
tags = tags.concat(tag);
|
|
||||||
// jadiin value string
|
|
||||||
let value = tags.join(",");
|
|
||||||
local.value = "";
|
|
||||||
local.render();
|
|
||||||
fm.data[field.name] = value;
|
|
||||||
fm.render();
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={field.disabled}
|
|
||||||
className={cx(
|
|
||||||
"c-flex-grow c-flex-1 c-items-center c-bg-transparent c-outline-none c-px-2 c-text-sm",
|
|
||||||
"c-max-w-full"
|
|
||||||
)}
|
|
||||||
spellCheck={false}
|
|
||||||
onFocus={() => {
|
|
||||||
console.log("focus?");
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
console.log("blur?");
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||||
import { useLocal } from "@/utils/use-local";
|
|
||||||
import parser from "any-date-parser";
|
|
||||||
import { AutoHeightTextarea } from "@/comps/custom/AutoHeightTextarea";
|
|
||||||
import { M } from "src/data/unitShortcuts";
|
|
||||||
import { format } from "date-fns";
|
|
||||||
import get from "lodash.get";
|
|
||||||
|
|
||||||
export const FieldToggle: FC<{
|
export const FieldToggle: FC<{
|
||||||
field: FieldLocal;
|
field: FieldLocal;
|
||||||
|
|
@ -14,13 +10,13 @@ export const FieldToggle: FC<{
|
||||||
}> = ({ field, fm, arg }) => {
|
}> = ({ field, fm, arg }) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
list: [] as any[],
|
list: [] as any[],
|
||||||
value:[] as any[]
|
value: [] as any[],
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const callback = (res: any[]) => {
|
const callback = (res: any[]) => {
|
||||||
local.list = res;
|
local.list = res;
|
||||||
if(Array.isArray(res)){
|
if (Array.isArray(res)) {
|
||||||
local.value = res.map((e) => get(e, arg.pk))
|
local.value = res.map((e) => get(e, arg.pk));
|
||||||
}
|
}
|
||||||
local.render();
|
local.render();
|
||||||
};
|
};
|
||||||
|
|
@ -28,54 +24,71 @@ export const FieldToggle: FC<{
|
||||||
if (res instanceof Promise) res.then(callback);
|
if (res instanceof Promise) res.then(callback);
|
||||||
else callback(res);
|
else callback(res);
|
||||||
}, []);
|
}, []);
|
||||||
let listValue = []
|
let value = arg.opt_get_value({
|
||||||
let value: any = fm.data[field.name];
|
fm,
|
||||||
let checked = local.value.indexOf(value) > 0 ? true: false;
|
name: field.name,
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
|
let checked = local.value.indexOf(value) > 0 ? true : false;
|
||||||
|
|
||||||
if(local.list.length < 2){
|
|
||||||
return <>Minimum dan maksimal 2 Data</>
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div className={cx("c-flex c-items-center c-justify-start c-w-full")}>
|
||||||
className={cx(
|
|
||||||
"c-flex c-items-center c-justify-start c-w-full"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<label className="c-flex c-items-center c-cursor-pointer">
|
<label className="c-flex c-items-center c-cursor-pointer">
|
||||||
<div className="c-mr-3 c-text-gray-700 c-font-medium">{get(local,"list[0].label")}</div>
|
<div className="c-mr-3 c-text-gray-700 c-font-medium">
|
||||||
|
{get(local, "list[0].label")}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"c-relative",
|
"c-relative",
|
||||||
css`
|
css`
|
||||||
input:checked ~ .dot {
|
input:checked ~ .dot {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
background-color: #48bb78;
|
}
|
||||||
|
input:checked ~ .dot-wrap {
|
||||||
|
background-color: #125ad6;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<input type="checkbox" id="toggleB" checked={checked} className="c-sr-only" onChange={(e) => {
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="toggleB"
|
||||||
|
checked={checked}
|
||||||
|
className="c-sr-only"
|
||||||
|
onChange={(e) => {
|
||||||
const check = e.target.checked;
|
const check = e.target.checked;
|
||||||
|
|
||||||
if(check){
|
if (check) {
|
||||||
fm.data[field.name] = local.value[1];
|
arg.opt_set_value({
|
||||||
}else{
|
fm,
|
||||||
fm.data[field.name] = local.value[0];
|
name: field.name,
|
||||||
|
selected: [local.list[1]?.value],
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
arg.opt_set_value({
|
||||||
|
fm,
|
||||||
|
name: field.name,
|
||||||
|
selected: [local.list[0]?.value],
|
||||||
|
options: local.list,
|
||||||
|
type: field.type,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
fm.render();
|
}}
|
||||||
console.log({data: fm.data})
|
/>
|
||||||
// if(val) value = local.list[0];
|
<div className="dot-wrap c-block c-bg-gray-600 c-w-8 c-h-5 c-rounded-full"></div>
|
||||||
// value = local.list[1]
|
|
||||||
}}/>
|
|
||||||
<div className="c-block c-bg-gray-600 c-w-8 c-h-5 c-rounded-full"></div>
|
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"dot c-absolute c-left-1 c-top-1 c-bg-white c-w-3 c-h-3 c-rounded-full c-transition"
|
"dot c-absolute c-left-1 c-top-1 c-bg-white c-w-3 c-h-3 c-rounded-full c-transition"
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="c-ml-3 c-text-gray-700 c-font-medium">{get(local,"list[1].label")}</div>
|
<div className="c-ml-3 c-text-gray-700 c-font-medium">
|
||||||
|
{get(local, "list[1].label")}
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { createItem } from "lib/gen/utils";
|
import { generateSelect } from "lib/comps/md/gen/md-select";
|
||||||
|
import { on_load } from "lib/comps/md/gen/tbl-list/on_load";
|
||||||
|
import { createItem, parseGenField } from "lib/gen/utils";
|
||||||
import capitalize from "lodash.capitalize";
|
import capitalize from "lodash.capitalize";
|
||||||
import { ArrowBigDown } from "lucide-react";
|
import { ArrowBigDown } from "lucide-react";
|
||||||
|
import { on_load_rel } from "./on_load_rel";
|
||||||
export type GFCol = {
|
export type GFCol = {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -16,7 +19,6 @@ export const newField = (
|
||||||
arg: GFCol,
|
arg: GFCol,
|
||||||
opt: { parent_table: string; value: Array<string> }
|
opt: { parent_table: string; value: Array<string> }
|
||||||
) => {
|
) => {
|
||||||
console.log({ arg, opt });
|
|
||||||
let type = "input";
|
let type = "input";
|
||||||
if (["int", "string", "text"].includes(arg.type)) {
|
if (["int", "string", "text"].includes(arg.type)) {
|
||||||
if (["int"].includes(arg.type)) {
|
if (["int"].includes(arg.type)) {
|
||||||
|
|
@ -67,6 +69,15 @@ export const newField = (
|
||||||
});
|
});
|
||||||
} else if (["has-many", "has-one"].includes(arg.type) && arg.relation) {
|
} else if (["has-many", "has-one"].includes(arg.type) && arg.relation) {
|
||||||
if (["has-one"].includes(arg.type)) {
|
if (["has-one"].includes(arg.type)) {
|
||||||
|
console.log(opt.value);
|
||||||
|
const fields = parseGenField(opt.value);
|
||||||
|
const res = generateSelect(fields);
|
||||||
|
const load = on_load_rel({
|
||||||
|
pk: res.pk,
|
||||||
|
table: arg.name,
|
||||||
|
select: res.select,
|
||||||
|
pks: {},
|
||||||
|
});
|
||||||
return createItem({
|
return createItem({
|
||||||
component: {
|
component: {
|
||||||
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
|
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
|
||||||
|
|
@ -76,15 +87,10 @@ export const newField = (
|
||||||
type: "single-option",
|
type: "single-option",
|
||||||
sub_type: "dropdown",
|
sub_type: "dropdown",
|
||||||
rel__gen_table: arg.name,
|
rel__gen_table: arg.name,
|
||||||
rel__gen_fields: [`[${opt.value.join(",")}]`],
|
// rel__gen_fields: [`[${opt.value.join(",")}]`],
|
||||||
opt__on_load: [
|
opt__on_load: [
|
||||||
`\
|
`\
|
||||||
() => {
|
${load}
|
||||||
console.log("halo");
|
|
||||||
return {
|
|
||||||
label: "halo", value: "value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
child: {
|
child: {
|
||||||
|
|
@ -127,6 +133,38 @@ export const newField = (
|
||||||
// },
|
// },
|
||||||
// };
|
// };
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
const fields = parseGenField(opt.value);
|
||||||
|
const res = generateSelect(fields);
|
||||||
|
const load = on_load_rel({
|
||||||
|
pk: res.pk,
|
||||||
|
table: arg.name,
|
||||||
|
select: res.select,
|
||||||
|
pks: {},
|
||||||
|
});
|
||||||
|
console.log(load)
|
||||||
|
|
||||||
|
return createItem({
|
||||||
|
component: {
|
||||||
|
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
|
||||||
|
props: {
|
||||||
|
name: arg.name,
|
||||||
|
label: formatName(arg.name),
|
||||||
|
type: "single-option",
|
||||||
|
sub_type: "dropdown",
|
||||||
|
rel__gen_table: arg.name,
|
||||||
|
// rel__gen_fields: [`[${opt.value.join(",")}]`],
|
||||||
|
opt__on_load: [
|
||||||
|
`\
|
||||||
|
${load}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
child: {
|
||||||
|
childs: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
return createItem({
|
return createItem({
|
||||||
component: {
|
component: {
|
||||||
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
|
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
|
||||||
|
|
@ -1,30 +1,30 @@
|
||||||
import { createItem, parseGenField } from "lib/gen/utils";
|
import { createItem, parseGenField } from "lib/gen/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { generateTableList } from "./gen-table-list";
|
import { newField } from "./fields";
|
||||||
import { generateSelect } from "./md-select";
|
import { generateSelect } from "../../md/gen/md-select";
|
||||||
import { on_load } from "./tbl-list/on_load";
|
import { on_load } from "../../md/gen/tbl-list/on_load";
|
||||||
import { on_submit } from "./tbl-list/on_submit";
|
import { on_submit } from "../../md/gen/tbl-list/on_submit";
|
||||||
import { newField } from "./form/fields";
|
|
||||||
import { createId } from "@paralleldrive/cuid2";
|
|
||||||
|
|
||||||
export const generateForm = async (
|
export const generateForm = async (
|
||||||
modify: (data: any) => void,
|
modify: (data: any) => void,
|
||||||
data: any,
|
data: {
|
||||||
|
gen__table: any;
|
||||||
|
gen__fields: any;
|
||||||
|
on_load: any;
|
||||||
|
on_submit: any;
|
||||||
|
body: any;
|
||||||
|
},
|
||||||
item: PrasiItem,
|
item: PrasiItem,
|
||||||
commit: boolean
|
commit: boolean
|
||||||
) => {
|
) => {
|
||||||
const table = JSON.parse(data.gen_table.value);
|
const table = JSON.parse(data.gen__table.value);
|
||||||
console.log("halo");
|
const raw_fields = JSON.parse(data.gen__fields.value) as (
|
||||||
console.log(table);
|
|
||||||
const raw_fields = JSON.parse(data.gen_fields.value) as (
|
|
||||||
| string
|
| string
|
||||||
| { value: string; checked: string[] }
|
| { value: string; checked: string[] }
|
||||||
)[];
|
)[];
|
||||||
let pk = "";
|
let pk = "";
|
||||||
console.log({ raw_fields });
|
|
||||||
let pks: Record<string, string> = {};
|
let pks: Record<string, string> = {};
|
||||||
const fields = parseGenField(raw_fields);
|
const fields = parseGenField(raw_fields);
|
||||||
// convert ke bahasa prisma untuk select
|
|
||||||
const res = generateSelect(fields);
|
const res = generateSelect(fields);
|
||||||
pk = res.pk;
|
pk = res.pk;
|
||||||
const select = res.select as any;
|
const select = res.select as any;
|
||||||
|
|
@ -33,8 +33,8 @@ export const generateForm = async (
|
||||||
alert("Failed to generate! Primary Key not found. ");
|
alert("Failed to generate! Primary Key not found. ");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log({ pk, table, select, pks })
|
||||||
if (pk) {
|
if (pk) {
|
||||||
console.log("masuk");
|
|
||||||
if (data["on_load"]) {
|
if (data["on_load"]) {
|
||||||
result.on_load = {
|
result.on_load = {
|
||||||
mode: "raw",
|
mode: "raw",
|
||||||
|
|
@ -49,18 +49,17 @@ export const generateForm = async (
|
||||||
}
|
}
|
||||||
result.body = data["body"];
|
result.body = data["body"];
|
||||||
|
|
||||||
console.log({ fields, result });
|
|
||||||
const childs = [];
|
const childs = [];
|
||||||
|
console.log({fields})
|
||||||
for (const item of fields.filter((e) => !e.is_pk)) {
|
for (const item of fields.filter((e) => !e.is_pk)) {
|
||||||
let value = [] as Array<string>;
|
let value = [] as Array<string>;
|
||||||
if(["has-one", "has-many"].includes(item.type)){
|
if (["has-one", "has-many"].includes(item.type)) {
|
||||||
value = get(item, "value.checked") as any;
|
value = get(item, "value.checked") as any;
|
||||||
}
|
}
|
||||||
const field = newField(item, { parent_table: table, value });
|
const field = newField(item, { parent_table: table, value });
|
||||||
childs.push(field);
|
childs.push(field);
|
||||||
}
|
}
|
||||||
if (commit) {
|
if (commit) {
|
||||||
const body = item.edit.childs[0] as PrasiItem;
|
|
||||||
item.edit.setProp("body", {
|
item.edit.setProp("body", {
|
||||||
mode: "jsx",
|
mode: "jsx",
|
||||||
value: createItem({
|
value: createItem({
|
||||||
|
|
@ -68,9 +67,7 @@ export const generateForm = async (
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
await item.edit.commit();
|
await item.edit.commit();
|
||||||
// console.log("done")
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
export const generateRelation = (
|
||||||
|
data: {
|
||||||
|
rel__gen_table: any;
|
||||||
|
rel__gen_field: any;
|
||||||
|
},
|
||||||
|
item: PrasiItem,
|
||||||
|
commit: boolean
|
||||||
|
) => {
|
||||||
|
console.log(data, item, commit);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
export const on_load_rel = ({
|
||||||
|
pk,
|
||||||
|
table,
|
||||||
|
select,
|
||||||
|
pks,
|
||||||
|
}: {
|
||||||
|
pk: string;
|
||||||
|
table: string;
|
||||||
|
select: any;
|
||||||
|
pks: Record<string, string>;
|
||||||
|
}) => {
|
||||||
|
const sample = {} as any;
|
||||||
|
const cols = [];
|
||||||
|
for (const [k, v] of Object.entries(select) as any) {
|
||||||
|
if(k !== pk && typeof v !== "object"){
|
||||||
|
cols.push(k);
|
||||||
|
}
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log({cols})
|
||||||
|
|
||||||
|
return `\
|
||||||
|
(arg: {
|
||||||
|
reload: () => Promise<void>;
|
||||||
|
orderBy?: Record<string, "asc" | "desc">;
|
||||||
|
paging: { take: number; skip: number };
|
||||||
|
mode: 'count' | 'query'
|
||||||
|
}) => {
|
||||||
|
if (isEditor) return [${JSON.stringify(sample)}];
|
||||||
|
|
||||||
|
return new Promise(async (done) => {
|
||||||
|
if (arg.mode === 'count') {
|
||||||
|
return await db.${table}.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = await db.${table}.findMany({
|
||||||
|
select: ${JSON.stringify(select)},
|
||||||
|
orderBy: arg.orderBy || {
|
||||||
|
${pk}: "desc"
|
||||||
|
},
|
||||||
|
...arg.paging,
|
||||||
|
});
|
||||||
|
if(items.length){
|
||||||
|
const cols = ${JSON.stringify(cols)};
|
||||||
|
const getLabel = (data: any) => {
|
||||||
|
const result = [];
|
||||||
|
cols.map((e) => {
|
||||||
|
if(data[e]){
|
||||||
|
result.push(data[e]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result.join(" - ");
|
||||||
|
}
|
||||||
|
done(items.map((e) => {
|
||||||
|
return {
|
||||||
|
value: e.${pk},
|
||||||
|
label: getLabel(e),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
done([])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -20,15 +20,17 @@ export type FMProps = {
|
||||||
on_load_deps?: any[];
|
on_load_deps?: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GenField = {
|
export type GenField =
|
||||||
name: string,
|
| {
|
||||||
is_pk: boolean,
|
name: string;
|
||||||
type: string,
|
is_pk: boolean;
|
||||||
optional: boolean,
|
type: string;
|
||||||
} | {
|
optional: boolean;
|
||||||
checked: GenField[],
|
}
|
||||||
value: GFCol
|
| {
|
||||||
};
|
checked: GenField[];
|
||||||
|
value: GFCol;
|
||||||
|
};
|
||||||
|
|
||||||
type FieldType =
|
type FieldType =
|
||||||
| "-"
|
| "-"
|
||||||
|
|
@ -56,8 +58,26 @@ export type FieldProp = {
|
||||||
width: "auto" | "full" | "¾" | "½" | "⅓" | "¼";
|
width: "auto" | "full" | "¾" | "½" | "⅓" | "¼";
|
||||||
_item: PrasiItem;
|
_item: PrasiItem;
|
||||||
custom?: () => CustomField;
|
custom?: () => CustomField;
|
||||||
on_load: () => any | Promise<any>;
|
on_load: (arg?: any) => any | Promise<any>;
|
||||||
on_row: (row: any) => string;
|
opt_get_label: (row: any) => string;
|
||||||
|
opt_get_value: (arg: {
|
||||||
|
options: { label: string; value: string; item?: string }[];
|
||||||
|
fm: FMLocal;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}) => any;
|
||||||
|
opt_set_value: (arg: {
|
||||||
|
selected: string[];
|
||||||
|
options: { label: string; value: string; item?: string }[];
|
||||||
|
fm: FMLocal;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}) => any;
|
||||||
|
opt_selected: (arg: {
|
||||||
|
item: { value: string; label: string; item?: any };
|
||||||
|
current: any;
|
||||||
|
options: { value: string; label: string; item?: any }[];
|
||||||
|
}) => boolean;
|
||||||
pk: string;
|
pk: string;
|
||||||
sub_type: string;
|
sub_type: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
|
@ -120,6 +140,9 @@ export type FieldInternal<T extends FieldProp["type"]> = {
|
||||||
input: Record<string, any> & {
|
input: Record<string, any> & {
|
||||||
render: () => void;
|
render: () => void;
|
||||||
};
|
};
|
||||||
|
options: {
|
||||||
|
on_load?: () => Promise<{ value: string; label: string }[]>;
|
||||||
|
};
|
||||||
prop?: any;
|
prop?: any;
|
||||||
};
|
};
|
||||||
export type FieldLocal = FieldInternal<any> & {
|
export type FieldLocal = FieldInternal<any> & {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ 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: Omit<FieldProp, 'name' | 'label'> & {
|
export const useField = (
|
||||||
|
arg: Omit<FieldProp, "name" | "label"> & {
|
||||||
name: string | (() => string);
|
name: string | (() => string);
|
||||||
label: 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: () => {
|
||||||
|
|
@ -14,9 +16,10 @@ export const useField = (arg: Omit<FieldProp, 'name' | 'label'> & {
|
||||||
input: {},
|
input: {},
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
const name = typeof arg.name === 'string' ? arg.name : arg.name();
|
const name = typeof arg.name === "string" ? arg.name : arg.name();
|
||||||
const label = typeof arg.label === 'string' ? arg.label : arg.label();
|
const label = typeof arg.label === "string" ? arg.label : arg.label();
|
||||||
const required = typeof arg.required === 'string' ? arg.required : arg.required();
|
const required =
|
||||||
|
typeof arg.required === "string" ? arg.required : arg.required();
|
||||||
|
|
||||||
const update_field = {
|
const update_field = {
|
||||||
name: name.replace(/\s*/gi, ""),
|
name: name.replace(/\s*/gi, ""),
|
||||||
|
|
@ -31,6 +34,7 @@ export const useField = (arg: Omit<FieldProp, 'name' | 'label'> & {
|
||||||
required_msg: arg.required_msg,
|
required_msg: arg.required_msg,
|
||||||
disabled: arg.disabled === "y",
|
disabled: arg.disabled === "y",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (field.status === "init" || isEditor) {
|
if (field.status === "init" || isEditor) {
|
||||||
for (const [k, v] of Object.entries(update_field)) {
|
for (const [k, v] of Object.entries(update_field)) {
|
||||||
(field as any)[k] = v;
|
(field as any)[k] = v;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
|
import { set } from "lib/utils/set";
|
||||||
import capitalize from "lodash.capitalize";
|
import capitalize from "lodash.capitalize";
|
||||||
import { GFCol, createItem, parseGenField } from "../../../gen/utils";
|
|
||||||
import { generateSelect } from "./md-select";
|
|
||||||
import { on_load } from "./tbl-list/on_load";
|
|
||||||
import { modeTableList } from "./mode-table-list";
|
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import set from "lodash.set";
|
import { createItem, parseGenField } from "../../../gen/utils";
|
||||||
|
import { generateSelect } from "./md-select";
|
||||||
|
import { modeTableList } from "./mode-table-list";
|
||||||
|
import { on_load } from "./tbl-list/on_load";
|
||||||
|
|
||||||
export const generateTableList = async (
|
export const generateTableList = async (
|
||||||
modify: (data: any) => void,
|
modify: (data: any) => void,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { Popover } from "../custom/Popover";
|
||||||
|
import { useLocal } from "lib/utils/use-local";
|
||||||
|
|
||||||
|
export type OptionItem = { value: string; label: string };
|
||||||
|
export const TypeaheadOptions: FC<{
|
||||||
|
popup?: boolean;
|
||||||
|
open?: boolean;
|
||||||
|
children: any;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
options: OptionItem[];
|
||||||
|
selected?: (arg: {
|
||||||
|
item: OptionItem;
|
||||||
|
options: OptionItem[];
|
||||||
|
idx: number;
|
||||||
|
}) => boolean;
|
||||||
|
onSelect?: (value: string) => void;
|
||||||
|
searching?: boolean;
|
||||||
|
width?: number;
|
||||||
|
}> = ({
|
||||||
|
popup,
|
||||||
|
children,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
options,
|
||||||
|
selected,
|
||||||
|
onSelect,
|
||||||
|
searching,
|
||||||
|
width,
|
||||||
|
}) => {
|
||||||
|
if (!popup) return children;
|
||||||
|
const local = useLocal({
|
||||||
|
selectedIdx: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={open}
|
||||||
|
arrow={false}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
backdrop={false}
|
||||||
|
placement="bottom-start"
|
||||||
|
className="c-flex-1"
|
||||||
|
content={
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
width
|
||||||
|
? css`
|
||||||
|
min-width: ${width}px;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
min-width: 150px;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{options.map((item, idx) => {
|
||||||
|
const is_selected = selected?.({ item, options, idx });
|
||||||
|
|
||||||
|
if (is_selected) {
|
||||||
|
local.selectedIdx = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
key={item.value + "_" + idx}
|
||||||
|
className={cx(
|
||||||
|
"c-px-3 c-py-1 cursor-pointer option-item text-sm",
|
||||||
|
is_selected
|
||||||
|
? "c-bg-blue-600 c-text-white"
|
||||||
|
: "hover:c-bg-blue-50",
|
||||||
|
idx > 0 && "c-border-t"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect?.(item.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{searching ? (
|
||||||
|
<div className="c-px-4 c-w-full c-text-xs c-text-slate-400">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{options.length === 0 && (
|
||||||
|
<div className="c-p-4 c-w-full c-text-center c-text-sm c-text-slate-400">
|
||||||
|
— Empty —
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { useLocal } from "lib/utils/use-local";
|
import { useLocal } from "lib/utils/use-local";
|
||||||
import { X } from "lucide-react";
|
import { ChevronDown, X } from "lucide-react";
|
||||||
import { FC, KeyboardEvent, useCallback, useEffect, useRef } from "react";
|
import { FC, KeyboardEvent, useCallback, useEffect, useRef } from "react";
|
||||||
import { Popover } from "../custom/Popover";
|
|
||||||
import { Badge } from "./badge";
|
import { Badge } from "./badge";
|
||||||
|
import { TypeaheadOptions } from "./typeahead-opt";
|
||||||
|
|
||||||
export const Typeahead: FC<{
|
export const Typeahead: FC<{
|
||||||
value?: string[];
|
value?: string[];
|
||||||
|
placeholder?: string;
|
||||||
options?: (arg: {
|
options?: (arg: {
|
||||||
search: string;
|
search: string;
|
||||||
existing: { value: string; label: string }[];
|
existing: { value: string; label: string }[];
|
||||||
|
|
@ -16,10 +17,14 @@ export const Typeahead: FC<{
|
||||||
search: string;
|
search: string;
|
||||||
item?: null | { value: string; label: string };
|
item?: null | { value: string; label: string };
|
||||||
}) => string | false;
|
}) => string | false;
|
||||||
|
onChange?: (selected: string[]) => void;
|
||||||
unique?: boolean;
|
unique?: boolean;
|
||||||
allowNew?: boolean;
|
allowNew?: boolean;
|
||||||
localSearch?: boolean;
|
localSearch?: boolean;
|
||||||
|
autoPopupWidth?: boolean;
|
||||||
focusOpen?: boolean;
|
focusOpen?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
mode?: "multi" | "single";
|
||||||
}> = ({
|
}> = ({
|
||||||
value,
|
value,
|
||||||
options: options_fn,
|
options: options_fn,
|
||||||
|
|
@ -28,6 +33,11 @@ export const Typeahead: FC<{
|
||||||
allowNew: allow_new,
|
allowNew: allow_new,
|
||||||
focusOpen: on_focus_open,
|
focusOpen: on_focus_open,
|
||||||
localSearch: local_search,
|
localSearch: local_search,
|
||||||
|
autoPopupWidth: auto_popup_width,
|
||||||
|
placeholder,
|
||||||
|
mode,
|
||||||
|
disabled,
|
||||||
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
value: [] as string[],
|
value: [] as string[],
|
||||||
|
|
@ -42,9 +52,12 @@ export const Typeahead: FC<{
|
||||||
result: null as null | { value: string; label: string }[],
|
result: null as null | { value: string; label: string }[],
|
||||||
},
|
},
|
||||||
unique: typeof unique === "undefined" ? true : unique,
|
unique: typeof unique === "undefined" ? true : unique,
|
||||||
allow_new: typeof allow_new === "undefined" ? true : allow_new,
|
allow_new: typeof allow_new === "undefined" ? false : allow_new,
|
||||||
on_focus_open: typeof on_focus_open === "undefined" ? false : on_focus_open,
|
on_focus_open: typeof on_focus_open === "undefined" ? true : on_focus_open,
|
||||||
local_search: typeof local_search === "undefined" ? true : local_search,
|
local_search: typeof local_search === "undefined" ? true : local_search,
|
||||||
|
mode: typeof mode === "undefined" ? "multi" : mode,
|
||||||
|
auto_popup_width:
|
||||||
|
typeof auto_popup_width === "undefined" ? false : auto_popup_width,
|
||||||
select: null as null | { value: string; label: string },
|
select: null as null | { value: string; label: string },
|
||||||
});
|
});
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
|
|
@ -55,6 +68,7 @@ export const Typeahead: FC<{
|
||||||
options.push({ value: local.search.input, label: local.search.input });
|
options.push({ value: local.search.input, label: local.search.input });
|
||||||
}
|
}
|
||||||
const added = new Set<string>();
|
const added = new Set<string>();
|
||||||
|
if (local.mode === "multi") {
|
||||||
options = options.filter((e) => {
|
options = options.filter((e) => {
|
||||||
if (!added.has(e.value)) added.add(e.value);
|
if (!added.has(e.value)) added.add(e.value);
|
||||||
else return false;
|
else return false;
|
||||||
|
|
@ -70,12 +84,15 @@ export const Typeahead: FC<{
|
||||||
if (!select_found) {
|
if (!select_found) {
|
||||||
local.select = options[0];
|
local.select = options[0];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isEditor) {
|
||||||
if (typeof value === "object" && value) {
|
if (typeof value === "object" && value) {
|
||||||
local.value = value;
|
local.value = value;
|
||||||
local.render();
|
local.render();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const select = useCallback(
|
const select = useCallback(
|
||||||
|
|
@ -110,17 +127,26 @@ export const Typeahead: FC<{
|
||||||
if (result) {
|
if (result) {
|
||||||
local.value.push(result);
|
local.value.push(result);
|
||||||
local.render();
|
local.render();
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let val = false as any;
|
||||||
if (arg.item) {
|
if (arg.item) {
|
||||||
local.value.push(arg.item.value);
|
local.value.push(arg.item.value);
|
||||||
|
val = arg.item.value;
|
||||||
} else {
|
} else {
|
||||||
if (!arg.search) return false;
|
if (!arg.search) return false;
|
||||||
local.value.push(arg.search);
|
local.value.push(arg.search);
|
||||||
|
val = arg.search;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof onChange === "function") {
|
||||||
|
onChange(local.value);
|
||||||
}
|
}
|
||||||
local.render();
|
local.render();
|
||||||
|
return val;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
@ -129,6 +155,14 @@ export const Typeahead: FC<{
|
||||||
|
|
||||||
const keydown = useCallback(
|
const keydown = useCallback(
|
||||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (!local.open) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
local.open = true;
|
||||||
|
local.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === "Backspace") {
|
if (e.key === "Backspace") {
|
||||||
if (local.value.length > 0 && e.currentTarget.selectionStart === 0) {
|
if (local.value.length > 0 && e.currentTarget.selectionStart === 0) {
|
||||||
local.value.pop();
|
local.value.pop();
|
||||||
|
|
@ -136,17 +170,32 @@ export const Typeahead: FC<{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
const selected = select({
|
const selected = select({
|
||||||
search: local.search.input,
|
search: local.search.input,
|
||||||
item: local.select,
|
item: local.select,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selected) {
|
if (local.mode === "single") {
|
||||||
resetSearch();
|
local.open = false;
|
||||||
local.render();
|
|
||||||
}
|
}
|
||||||
|
if (typeof selected === "string") {
|
||||||
|
resetSearch();
|
||||||
|
if (local.mode === "single") {
|
||||||
|
const item = local.options.find((item) => item.value === selected);
|
||||||
|
if (item) {
|
||||||
|
local.search.input = item.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (options.length > 0) {
|
if (options.length > 0) {
|
||||||
|
local.open = true;
|
||||||
if (e.key === "ArrowDown") {
|
if (e.key === "ArrowDown") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const idx = options.findIndex((item) => {
|
const idx = options.findIndex((item) => {
|
||||||
|
|
@ -170,10 +219,10 @@ export const Typeahead: FC<{
|
||||||
if (item.value === local.select?.value) return true;
|
if (item.value === local.select?.value) return true;
|
||||||
});
|
});
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
if (idx + 1 < options.length) {
|
if (idx - 1 >= 0) {
|
||||||
local.select = options[idx + 1];
|
local.select = options[idx - 1];
|
||||||
} else {
|
} else {
|
||||||
local.select = options[0];
|
local.select = options[options.length - 1];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
local.select = options[0];
|
local.select = options[0];
|
||||||
|
|
@ -220,19 +269,34 @@ export const Typeahead: FC<{
|
||||||
clearTimeout(local.search.timeout);
|
clearTimeout(local.search.timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (local.mode === "single" && local.value.length > 1) {
|
||||||
|
local.value = [local.value.pop() || ""];
|
||||||
|
}
|
||||||
|
const valueLabel = local.value.map((value) => {
|
||||||
|
const item = local.options.find((item) => item.value === value);
|
||||||
|
|
||||||
|
if (local.mode === "single") {
|
||||||
|
if (!local.open) {
|
||||||
|
local.select = item || null;
|
||||||
|
local.search.input = item?.label || "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"c-flex c-cursor-text c-space-x-2 c-flex-wrap c-p-2 c-pb-0 c-items-center c-w-full c-h-full c-flex-1",
|
local.mode === "single" ? "c-cursor-pointer" : "c-cursor-text",
|
||||||
css`
|
"c-flex c-relative c-space-x-2 c-flex-wrap c-pt-2 c-px-2 c-pb-0 c-items-center c-w-full c-h-full c-flex-1"
|
||||||
min-height: 40px;
|
|
||||||
`
|
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
input.current?.focus();
|
input.current?.focus();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{local.value.map((e, idx) => {
|
{local.mode === "multi" ? (
|
||||||
|
<>
|
||||||
|
{valueLabel.map((e, idx) => {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
key={idx}
|
key={idx}
|
||||||
|
|
@ -241,19 +305,23 @@ export const Typeahead: FC<{
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
local.value = local.value.filter((val) => e !== val);
|
local.value = local.value.filter((val) => e?.value !== val);
|
||||||
local.render();
|
local.render();
|
||||||
input.current?.focus();
|
input.current?.focus();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>{e}</div>
|
<div>{e?.label}</div>
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
<WrapOptions
|
<TypeaheadOptions
|
||||||
wrap={true}
|
popup={true}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
local.select = null;
|
local.select = null;
|
||||||
|
|
@ -267,19 +335,32 @@ export const Typeahead: FC<{
|
||||||
onSelect={(value) => {
|
onSelect={(value) => {
|
||||||
local.open = false;
|
local.open = false;
|
||||||
local.value.push(value);
|
local.value.push(value);
|
||||||
|
|
||||||
resetSearch();
|
resetSearch();
|
||||||
|
if (local.mode === "single") {
|
||||||
|
const item = local.options.find((item) => item.value === value);
|
||||||
|
if (item) {
|
||||||
|
local.search.input = item.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
local.render();
|
local.render();
|
||||||
}}
|
}}
|
||||||
selected={local.select?.value}
|
width={local.auto_popup_width ? input.current?.offsetWidth : undefined}
|
||||||
|
selected={({ item, options, idx }) => {
|
||||||
|
if (item.value === local.select?.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
placeholder={local.mode === "multi" ? placeholder : ""}
|
||||||
type="text"
|
type="text"
|
||||||
ref={input}
|
ref={input}
|
||||||
value={local.search.input}
|
value={local.search.input}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
|
||||||
onFocus={(e) => {
|
|
||||||
if (!local.open) {
|
if (!local.open) {
|
||||||
if (local.on_focus_open) {
|
if (local.on_focus_open) {
|
||||||
openOptions();
|
openOptions();
|
||||||
|
|
@ -313,6 +394,15 @@ export const Typeahead: FC<{
|
||||||
local.search.result = local.options.filter((e) =>
|
local.search.result = local.options.filter((e) =>
|
||||||
e.label.toLowerCase().includes(search)
|
e.label.toLowerCase().includes(search)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
local.search.result.length > 0 &&
|
||||||
|
!local.search.result.find(
|
||||||
|
(e) => e.value === local.select?.value
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
local.select = local.search.result[0];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
local.search.result = null;
|
local.search.result = null;
|
||||||
}
|
}
|
||||||
|
|
@ -335,7 +425,6 @@ export const Typeahead: FC<{
|
||||||
});
|
});
|
||||||
local.search.searching = false;
|
local.search.searching = false;
|
||||||
local.search.promise = null;
|
local.search.promise = null;
|
||||||
local.render();
|
|
||||||
} else {
|
} else {
|
||||||
local.search.result = result.map((item) => {
|
local.search.result = result.map((item) => {
|
||||||
if (typeof item === "string")
|
if (typeof item === "string")
|
||||||
|
|
@ -343,95 +432,43 @@ export const Typeahead: FC<{
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
local.search.searching = false;
|
local.search.searching = false;
|
||||||
local.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
local.search.result.length > 0 &&
|
||||||
|
!local.search.result.find(
|
||||||
|
(e) => e.value === local.select?.value
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
local.select = local.search.result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
local.render();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
className={cx("c-flex-1 c-mb-2 c-text-sm c-outline-none")}
|
className={cx(
|
||||||
|
"c-flex-1 c-mb-2 c-text-sm c-outline-none",
|
||||||
|
local.mode === "single" ? "c-cursor-pointer" : ""
|
||||||
|
)}
|
||||||
onKeyDown={keydown}
|
onKeyDown={keydown}
|
||||||
/>
|
/>
|
||||||
</WrapOptions>
|
</TypeaheadOptions>
|
||||||
|
|
||||||
|
{local.mode === "single" && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"c-absolute c-pointer-events-none c-z-10 c-inset-0 c-left-auto c-flex c-items-center ",
|
||||||
|
"c-bg-white c-justify-center c-w-6 c-mr-1 c-my-2",
|
||||||
|
disabled && "c-hidden"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronDown size={14} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const WrapOptions: FC<{
|
|
||||||
wrap: boolean;
|
|
||||||
children: any;
|
|
||||||
open: boolean;
|
|
||||||
onOpenChange: (open: boolean) => void;
|
|
||||||
options: { value: string; label: string }[];
|
|
||||||
selected?: string;
|
|
||||||
onSelect: (value: string) => void;
|
|
||||||
searching?: boolean;
|
|
||||||
}> = ({
|
|
||||||
wrap,
|
|
||||||
children,
|
|
||||||
open,
|
|
||||||
onOpenChange,
|
|
||||||
options,
|
|
||||||
selected,
|
|
||||||
onSelect,
|
|
||||||
searching,
|
|
||||||
}) => {
|
|
||||||
if (!wrap) return children;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
open={open}
|
|
||||||
arrow={false}
|
|
||||||
onOpenChange={onOpenChange}
|
|
||||||
placement="bottom-start"
|
|
||||||
content={
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
css`
|
|
||||||
min-width: 150px;
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{options.map((item, idx) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
tabIndex={0}
|
|
||||||
key={item.value + "_" + idx}
|
|
||||||
className={cx(
|
|
||||||
"c-px-3 c-py-1 cursor-pointer option-item",
|
|
||||||
item.value === selected
|
|
||||||
? "c-bg-blue-600 c-text-white"
|
|
||||||
: "hover:c-bg-blue-50",
|
|
||||||
idx > 0 && "c-border-t"
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
onSelect(item.value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{searching ? (
|
|
||||||
<div className="c-px-4 c-w-full c-text-xs c-text-slate-400">
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{options.length === 0 && (
|
|
||||||
<div className="c-p-4 c-w-full c-text-center c-text-sm c-text-slate-400">
|
|
||||||
— Empty —
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export const FilterField = lazify(
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Generator */
|
/** Generator */
|
||||||
export { generateMasterDetail } from "lib/comps/md/gen/md-gen";
|
export { generateMasterDetail } from "@/comps/md/gen/md-gen";
|
||||||
|
|
||||||
/** ETC */
|
/** ETC */
|
||||||
export { filterWhere } from "@/comps/filter/utils/filter-where";
|
export { filterWhere } from "@/comps/filter/utils/filter-where";
|
||||||
|
|
@ -55,12 +55,12 @@ export {
|
||||||
export { MasterDetailType } from "@/comps/md/utils/typings";
|
export { MasterDetailType } from "@/comps/md/utils/typings";
|
||||||
export { FormatValue } from "@/utils/format-value";
|
export { FormatValue } from "@/utils/format-value";
|
||||||
export { GetValue } from "@/utils/get-value";
|
export { GetValue } from "@/utils/get-value";
|
||||||
export { TableListType } from "lib/comps/list/utils/typings";
|
export { TableListType } from "@/comps/list/utils/typings";
|
||||||
export { Button, FloatButton } from "@/comps/ui/button";
|
export { Button, FloatButton } from "@/comps/ui/button";
|
||||||
export { prasi_gen } from "@/gen/prasi_gen";
|
export { prasi_gen } from "@/gen/prasi_gen";
|
||||||
export { password } from "@/utils/password";
|
export { password } from "@/utils/password";
|
||||||
export { generateTableList } from "@/comps/md/gen/gen-table-list";
|
export { generateTableList } from "@/comps/md/gen/gen-table-list";
|
||||||
export { generateForm } from "@/comps/md/gen/gen-form";
|
export { generateForm } from "@/comps/form/gen/gen-form";
|
||||||
|
|
||||||
/** Session */
|
/** Session */
|
||||||
export {
|
export {
|
||||||
|
|
@ -97,7 +97,7 @@ export { Profile } from "@/preset/profile/Profile";
|
||||||
export { generateProfile } from "@/preset/profile/utils/generate";
|
export { generateProfile } from "@/preset/profile/utils/generate";
|
||||||
export { ButtonUpload } from "@/preset/profile/ButtonUpload";
|
export { ButtonUpload } from "@/preset/profile/ButtonUpload";
|
||||||
export { longDate, shortDate, timeAgo, formatTime } from "@/utils/date";
|
export { longDate, shortDate, timeAgo, formatTime } from "@/utils/date";
|
||||||
|
export { getPathname } from "./utils/pathname";
|
||||||
|
|
||||||
export * from '@/comps/ui/typeahead'
|
export * from '@/comps/ui/typeahead'
|
||||||
export * from '@/comps/ui/input'
|
export * from '@/comps/ui/input'
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import set from "lodash.set";
|
import { set } from "lib/utils/set";
|
||||||
|
|
||||||
const cache: any = [];
|
const cache: any = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ export type RGSession = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logout = (url_login?: string) => {
|
export const logout = (url_login?: string) => {
|
||||||
console.log("halo")
|
|
||||||
if (typeof get(w, "user") === "object") {
|
if (typeof get(w, "user") === "object") {
|
||||||
w.user = null;
|
w.user = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import set from "lodash.set";
|
import { set } from "lib/utils/set";
|
||||||
|
|
||||||
export const select = (rel: any) => {
|
export const select = (rel: any) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { select } from "@/preset/login/utils/select";
|
import { select } from "@/preset/login/utils/select";
|
||||||
|
import { set } from "lib/utils/set";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import set from "lodash.set";
|
|
||||||
|
|
||||||
type typeFieldLogin = {
|
type typeFieldLogin = {
|
||||||
upload: string;
|
upload: string;
|
||||||
|
|
@ -29,7 +29,7 @@ export const generateProfile = async (
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log({btn})
|
console.log({ btn });
|
||||||
if (btn) {
|
if (btn) {
|
||||||
const upload = btn.edit.childs.find(
|
const upload = btn.edit.childs.find(
|
||||||
(e) => get(e, "component.id") === "296825f3-dac7-4a13-8871-9743718bc411"
|
(e) => get(e, "component.id") === "296825f3-dac7-4a13-8871-9743718bc411"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
export function set<T extends object, V>(
|
||||||
|
obj: T,
|
||||||
|
keys: string | ArrayLike<string | number>,
|
||||||
|
value: V
|
||||||
|
): void {
|
||||||
|
if (typeof keys === "string") {
|
||||||
|
keys = keys.split(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 0,
|
||||||
|
l = keys.length,
|
||||||
|
t = obj as any,
|
||||||
|
x,
|
||||||
|
k;
|
||||||
|
|
||||||
|
if (Array.isArray(keys)) {
|
||||||
|
while (i < l) {
|
||||||
|
k = keys[i++];
|
||||||
|
if (k === "__proto__" || k === "constructor" || k === "prototype") break;
|
||||||
|
t = t[k] =
|
||||||
|
i === l
|
||||||
|
? value
|
||||||
|
: typeof (x = t[k]) === typeof keys
|
||||||
|
? x
|
||||||
|
: keys[i] * 0 !== 0 || !!~("" + keys[i]).indexOf(".")
|
||||||
|
? {}
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,14 +8,17 @@ export const useLocal = <T extends object>(
|
||||||
deps?: any[]
|
deps?: any[]
|
||||||
): {
|
): {
|
||||||
[K in keyof T]: T[K] extends Promise<any> ? null | Awaited<T[K]> : T[K];
|
[K in keyof T]: T[K] extends Promise<any> ? null | Awaited<T[K]> : T[K];
|
||||||
} & { render: (force?: boolean) => void } => {
|
} & { render: () => void } => {
|
||||||
const [, _render] = useState({});
|
const [, _render] = useState({});
|
||||||
const _ = useRef({
|
const _ = useRef({
|
||||||
data: data as unknown as T & {
|
data: data as unknown as T & {
|
||||||
render: (force?: boolean) => void;
|
render: () => void;
|
||||||
},
|
},
|
||||||
deps: (deps || []) as any[],
|
deps: (deps || []) as any[],
|
||||||
ready: false,
|
ready: false,
|
||||||
|
_loading: {} as any,
|
||||||
|
lastRender: 0,
|
||||||
|
lastRenderCount: 0,
|
||||||
});
|
});
|
||||||
const local = _.current;
|
const local = _.current;
|
||||||
|
|
||||||
|
|
@ -25,9 +28,23 @@ export const useLocal = <T extends object>(
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (local.ready === false) {
|
if (local.ready === false) {
|
||||||
local.data.render = (force) => {
|
local._loading = {};
|
||||||
if (force) _render({});
|
|
||||||
else if (local.ready) _render({});
|
local.data.render = () => {
|
||||||
|
if (local.ready) {
|
||||||
|
if (Date.now() - local.lastRender < 200) {
|
||||||
|
local.lastRenderCount++;
|
||||||
|
} else {
|
||||||
|
local.lastRenderCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (local.lastRenderCount > 20) {
|
||||||
|
throw new Error("local.render more than 20 times in less than 200ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
local.lastRender = Date.now();
|
||||||
|
_render({});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (local.deps.length > 0 && deps) {
|
if (local.deps.length > 0 && deps) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue