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 const Form: FC<FMProps> = (props) => {
|
||||
|
||||
const { PassProp, body } = props;
|
||||
const fm = useLocal<FMInternal>({
|
||||
data: editorFormData[props.item.id]
|
||||
|
|
@ -121,6 +120,7 @@ export const Form: FC<FMProps> = (props) => {
|
|||
}, 100);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ReactNode } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
||||
import { Label } from "../field/Label";
|
||||
import { FieldLoading } from "../field/raw/FieldLoading";
|
||||
import { FieldLoading } from "../../ui/field-loading";
|
||||
|
||||
export const BaseField = (prop: {
|
||||
field: FieldLocal;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { FC, isValidElement } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
||||
import { FieldLoading } from "./raw/FieldLoading";
|
||||
import { FieldLoading } from "../../ui/field-loading";
|
||||
import { MultiOption } from "./type/TypeMultiOption";
|
||||
import { SingleOption } from "./type/TypeSingleOption";
|
||||
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 }) => {
|
||||
const local = useLocal({
|
||||
list: [] as any[],
|
||||
value: [] as any[],
|
||||
});
|
||||
useEffect(() => {
|
||||
const callback = (res: any[]) => {
|
||||
local.list = res;
|
||||
if (Array.isArray(res)) {
|
||||
local.value = res.map((e) => get(e, arg.pk));
|
||||
}
|
||||
local.render();
|
||||
};
|
||||
const res = arg.on_load();
|
||||
if (res instanceof Promise) res.then(callback);
|
||||
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") {
|
||||
value = fm.data[field.name] || [];
|
||||
|
|
@ -42,32 +43,45 @@ export const FieldButton: FC<{
|
|||
{local.list.map((item) => {
|
||||
let isChecked = false;
|
||||
try {
|
||||
isChecked = value.some((e: any) => e[arg.pk] === item[arg.pk]);
|
||||
isChecked = value.some((e: any) => e === item[arg.pk]);
|
||||
} catch (ex) {}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!Array.isArray(fm.data[field.name]))
|
||||
fm.data[field.name] = [];
|
||||
let selected = Array.isArray(value)
|
||||
? value.map((row) => {
|
||||
return local.list.find((e) => e.value === row);
|
||||
})
|
||||
: [];
|
||||
if (isChecked) {
|
||||
fm.data[field.name] = fm.data[field.name].filter(
|
||||
(e: any) => e[arg.pk] !== item[arg.pk]
|
||||
selected = selected.filter(
|
||||
(e: any) => e.value !== item.value
|
||||
);
|
||||
} 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"
|
||||
role="button"
|
||||
title="Hover chip"
|
||||
className={cx(
|
||||
isChecked ? "c-bg-gray-200" : "c-border c-border-gray-500",
|
||||
" 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 "
|
||||
isChecked
|
||||
? "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">
|
||||
{arg.on_row(item)}
|
||||
{arg.opt_get_label(item)}
|
||||
</span>
|
||||
</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-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`
|
||||
grid-template-columns: repeat(
|
||||
${local.list.length},
|
||||
|
|
@ -99,15 +113,20 @@ export const FieldButton: FC<{
|
|||
<div>
|
||||
<label
|
||||
onClick={() => {
|
||||
fm.data[field.name] = get(e, arg.pk);
|
||||
fm.render();
|
||||
arg.opt_set_value({
|
||||
fm,
|
||||
name: field.name,
|
||||
selected: [e.value],
|
||||
options: local.list,
|
||||
type: field.type,
|
||||
});
|
||||
}}
|
||||
className={cx(
|
||||
`${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>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,22 +10,24 @@ export const FieldCheckbox: FC<{
|
|||
}> = ({ field, fm, arg }) => {
|
||||
const local = useLocal({
|
||||
list: [] as any[],
|
||||
value: [] as any[],
|
||||
});
|
||||
useEffect(() => {
|
||||
const callback = (res: any[]) => {
|
||||
local.list = res;
|
||||
if (Array.isArray(res)) {
|
||||
local.value = res.map((e) => get(e, arg.pk));
|
||||
}
|
||||
local.render();
|
||||
};
|
||||
const res = arg.on_load();
|
||||
if (res instanceof Promise) res.then(callback);
|
||||
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 (
|
||||
<>
|
||||
<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) => {
|
||||
let isChecked = false;
|
||||
try {
|
||||
isChecked = value.some((e: any) => e[arg.pk] === item[arg.pk]);
|
||||
isChecked = value.some((e: any) => e === item[arg.pk]);
|
||||
} catch (ex) {}
|
||||
console.log(item[arg.pk], isChecked)
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
console.log(item);
|
||||
if (!Array.isArray(fm.data[field.name]))
|
||||
fm.data[field.name] = [];
|
||||
console.log(isChecked);
|
||||
let selected = Array.isArray(value)
|
||||
? value.map((row) => {
|
||||
return local.list.find((e) => e.value === row);
|
||||
})
|
||||
: [];
|
||||
if (isChecked) {
|
||||
fm.data[field.name] = fm.data[field.name].filter(
|
||||
(e: any) => e[arg.pk] !== item[arg.pk]
|
||||
selected = selected.filter(
|
||||
(e: any) => e.value !== item.value
|
||||
);
|
||||
} 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"
|
||||
>
|
||||
|
|
@ -81,7 +90,7 @@ export const FieldCheckbox: FC<{
|
|||
/>
|
||||
</svg>
|
||||
)}
|
||||
<div className="">{arg.on_row(item)}</div>
|
||||
<div className="">{arg.opt_get_label(item)}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,97 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import { OptionItem, RawDropdown } from "../raw/Dropdown";
|
||||
import { FieldLoading } from "../raw/FieldLoading";
|
||||
import get from "lodash.get";
|
||||
import { FC, useEffect } from "react";
|
||||
import { Typeahead } from "../../../../..";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
import { FieldLoading } from "lib/comps/ui/field-loading";
|
||||
|
||||
export const TypeDropdown: FC<{
|
||||
field: FieldLocal;
|
||||
fm: FMLocal;
|
||||
arg: FieldProp;
|
||||
}> = ({ field, fm, arg }) => {
|
||||
const input = useLocal({
|
||||
list: null as null | any[],
|
||||
pk: "",
|
||||
const local = useLocal({
|
||||
loaded: false,
|
||||
options: [],
|
||||
});
|
||||
const value = fm.data[field.name];
|
||||
field.input = input;
|
||||
let value = arg.opt_get_value({
|
||||
fm,
|
||||
name: field.name,
|
||||
options: local.options,
|
||||
type: field.type,
|
||||
});
|
||||
console.log({ value });
|
||||
useEffect(() => {
|
||||
if (!isEditor && input.list === null) {
|
||||
field.status = "loading";
|
||||
input.pk = arg.pk;
|
||||
field.render();
|
||||
const callback = (arg: any[]) => {
|
||||
input.list = arg;
|
||||
field.status = "ready";
|
||||
input.render();
|
||||
};
|
||||
const res = arg.on_load();
|
||||
if (res instanceof Promise) res.then(callback);
|
||||
else callback(res);
|
||||
if (typeof arg.on_load === "function") {
|
||||
const options = arg.on_load({ mode: "query" });
|
||||
console.log("Masuk");
|
||||
// console.log(options)
|
||||
if (options instanceof Promise) {
|
||||
options.then((res) => {
|
||||
console.log({ res });
|
||||
local.options = res;
|
||||
local.loaded = true;
|
||||
local.render();
|
||||
});
|
||||
} else {
|
||||
local.options = options;
|
||||
local.render();
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
let list: OptionItem[] = [];
|
||||
if (input.list && input.list.length) {
|
||||
input.list.map((e: any) => {
|
||||
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;
|
||||
}
|
||||
if (!local.loaded) return <FieldLoading />;
|
||||
|
||||
if (field.type === "single-option")
|
||||
return (
|
||||
<>
|
||||
{field.status === "loading" ? (
|
||||
<FieldLoading />
|
||||
) : (
|
||||
<RawDropdown
|
||||
options={list}
|
||||
value={selected}
|
||||
onChange={(val) => {
|
||||
if (val === null) {
|
||||
fm.data[field.name] = null;
|
||||
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;
|
||||
}
|
||||
}
|
||||
<Typeahead
|
||||
value={value}
|
||||
onSelect={({ search, item }) => {
|
||||
if (item) {
|
||||
arg.opt_set_value({
|
||||
fm,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
options: local.options,
|
||||
selected: [item.value],
|
||||
});
|
||||
}
|
||||
|
||||
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"
|
||||
disabled={field.disabled}
|
||||
onFocus={() => {
|
||||
field.focused = true;
|
||||
field.render();
|
||||
}}
|
||||
onBlur={() => {
|
||||
field.focused = false;
|
||||
field.render();
|
||||
allowNew={false}
|
||||
autoPopupWidth={true}
|
||||
focusOpen={true}
|
||||
mode={"single"}
|
||||
placeholder={arg.placeholder}
|
||||
options={() => {
|
||||
return local.options;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
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 { useLocal } from "@/utils/use-local";
|
||||
import get from "lodash.get";
|
||||
import { TypeDropdown } from "./TypeDropdown";
|
||||
import { FieldToggle } from "./TypeToggle";
|
||||
import { FieldButton } from "./TypeButton";
|
||||
import { FieldRadio } from "./TypeRadio";
|
||||
import { FieldCheckbox } from "./TypeCheckbox";
|
||||
import { TypeDropdown } from "./TypeDropdown";
|
||||
import { FieldTag } from "./TypeTag";
|
||||
|
||||
export const MultiOption: FC<{
|
||||
|
|
@ -16,7 +12,9 @@ export const MultiOption: FC<{
|
|||
}> = ({ field, fm, arg }) => {
|
||||
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}/>
|
||||
): arg.sub_type === "button" ? (
|
||||
<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 get from "lodash.get";
|
||||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
|
||||
export const FieldRadio: FC<{
|
||||
field: FieldLocal;
|
||||
|
|
@ -24,9 +24,13 @@ export const FieldRadio: FC<{
|
|||
if (res instanceof Promise) res.then(callback);
|
||||
else callback(res);
|
||||
}, []);
|
||||
let listValue = [];
|
||||
let value: any = fm.data[field.name];
|
||||
let checked = local.value.indexOf(value) > 0 ? true : false;
|
||||
|
||||
let value = arg.opt_get_value({
|
||||
fm,
|
||||
name: field.name,
|
||||
options: local.list,
|
||||
type: field.type,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div className={cx("c-flex c-items-center c-w-full c-flex-row")}>
|
||||
|
|
@ -36,38 +40,24 @@ export const FieldRadio: FC<{
|
|||
<div
|
||||
className="flex items-center mb-4"
|
||||
onClick={() => {
|
||||
fm.data[field.name] = get(e, arg.pk);
|
||||
fm.render();
|
||||
arg.opt_set_value({
|
||||
fm,
|
||||
name: field.name,
|
||||
selected: [e.value],
|
||||
options: local.list,
|
||||
type: field.type,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<input
|
||||
id="country-option-1"
|
||||
type="radio"
|
||||
name="countries"
|
||||
value="USA"
|
||||
className="h-4 w-4 border-gray-300 focus:ring-2 focus:ring-blue-300"
|
||||
aria-labelledby="country-option-1"
|
||||
aria-describedby="country-option-1"
|
||||
checked={get(e, arg.pk) === value}
|
||||
/>
|
||||
<label className="text-sm font-medium text-gray-900 ml-2 block">
|
||||
{arg.on_row(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)}
|
||||
{arg.opt_get_label(e)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useLocal } from "@/utils/use-local";
|
|||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal } from "../../typings";
|
||||
import { OptionItem, RawDropdown } from "../raw/Dropdown";
|
||||
import { FieldLoading } from "../raw/FieldLoading";
|
||||
import { FieldLoading } from "../../../ui/field-loading";
|
||||
|
||||
export type PropTypeRelation = {
|
||||
type: "has-one" | "has-many";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
import { Typeahead } from "../../../../..";
|
||||
|
||||
export const FieldTag: FC<{
|
||||
field: FieldLocal;
|
||||
|
|
@ -13,95 +14,20 @@ export const FieldTag: FC<{
|
|||
value: null as any,
|
||||
});
|
||||
let value: any = fm.data[field.name];
|
||||
let tags: Array<string> = typeof value === "string" ? value.split(",") : [];
|
||||
if(isEditor){
|
||||
tags = ["sample","sample"]
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
|
||||
<div
|
||||
className={cx(
|
||||
"c-px-2 c-flex c-flex-row c-items-center c-flex-wrap c-flex-grow c-gap-1 c-m-1"
|
||||
)}
|
||||
onClick={() => {
|
||||
if (local.ref) {
|
||||
local.ref.focus();
|
||||
}
|
||||
<Typeahead
|
||||
value={value}
|
||||
onSelect={({ search, item }) => {
|
||||
return item?.value || search;
|
||||
}}
|
||||
>
|
||||
{tags.map((item) => {
|
||||
return (
|
||||
<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">
|
||||
<span className="c-flex-grow c-px-2.5 c-py-0.5">{item}</span>
|
||||
<svg
|
||||
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?");
|
||||
allowNew
|
||||
focusOpen={false}
|
||||
placeholder={arg.placeholder}
|
||||
options={async () => {
|
||||
if (typeof arg.on_load === "function") return await arg.on_load();
|
||||
return [];
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import get from "lodash.get";
|
||||
import { FC, useEffect } from "react";
|
||||
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<{
|
||||
field: FieldLocal;
|
||||
|
|
@ -14,13 +10,13 @@ export const FieldToggle: FC<{
|
|||
}> = ({ field, fm, arg }) => {
|
||||
const local = useLocal({
|
||||
list: [] as any[],
|
||||
value:[] as any[]
|
||||
value: [] as any[],
|
||||
});
|
||||
useEffect(() => {
|
||||
const callback = (res: any[]) => {
|
||||
local.list = res;
|
||||
if(Array.isArray(res)){
|
||||
local.value = res.map((e) => get(e, arg.pk))
|
||||
if (Array.isArray(res)) {
|
||||
local.value = res.map((e) => get(e, arg.pk));
|
||||
}
|
||||
local.render();
|
||||
};
|
||||
|
|
@ -28,54 +24,71 @@ export const FieldToggle: FC<{
|
|||
if (res instanceof Promise) res.then(callback);
|
||||
else callback(res);
|
||||
}, []);
|
||||
let listValue = []
|
||||
let value: any = fm.data[field.name];
|
||||
let checked = local.value.indexOf(value) > 0 ? true: false;
|
||||
let value = arg.opt_get_value({
|
||||
fm,
|
||||
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 (
|
||||
<>
|
||||
<div
|
||||
className={cx(
|
||||
"c-flex c-items-center c-justify-start c-w-full"
|
||||
)}
|
||||
>
|
||||
<div className={cx("c-flex c-items-center c-justify-start c-w-full")}>
|
||||
<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
|
||||
className={cx(
|
||||
"c-relative",
|
||||
css`
|
||||
input:checked ~ .dot {
|
||||
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;
|
||||
|
||||
if(check){
|
||||
fm.data[field.name] = local.value[1];
|
||||
}else{
|
||||
fm.data[field.name] = local.value[0];
|
||||
if (check) {
|
||||
arg.opt_set_value({
|
||||
fm,
|
||||
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];
|
||||
// value = local.list[1]
|
||||
}}/>
|
||||
<div className="c-block c-bg-gray-600 c-w-8 c-h-5 c-rounded-full"></div>
|
||||
}}
|
||||
/>
|
||||
<div className="dot-wrap c-block c-bg-gray-600 c-w-8 c-h-5 c-rounded-full"></div>
|
||||
<div
|
||||
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"
|
||||
)}
|
||||
></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>
|
||||
</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 { ArrowBigDown } from "lucide-react";
|
||||
import { on_load_rel } from "./on_load_rel";
|
||||
export type GFCol = {
|
||||
name: string;
|
||||
type: string;
|
||||
|
|
@ -16,7 +19,6 @@ export const newField = (
|
|||
arg: GFCol,
|
||||
opt: { parent_table: string; value: Array<string> }
|
||||
) => {
|
||||
console.log({ arg, opt });
|
||||
let type = "input";
|
||||
if (["int", "string", "text"].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) {
|
||||
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({
|
||||
component: {
|
||||
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
|
||||
|
|
@ -76,15 +87,10 @@ export const newField = (
|
|||
type: "single-option",
|
||||
sub_type: "dropdown",
|
||||
rel__gen_table: arg.name,
|
||||
rel__gen_fields: [`[${opt.value.join(",")}]`],
|
||||
// rel__gen_fields: [`[${opt.value.join(",")}]`],
|
||||
opt__on_load: [
|
||||
`\
|
||||
() => {
|
||||
console.log("halo");
|
||||
return {
|
||||
label: "halo", value: "value"
|
||||
}
|
||||
}
|
||||
${load}
|
||||
`,
|
||||
],
|
||||
child: {
|
||||
|
|
@ -127,6 +133,38 @@ export const newField = (
|
|||
// },
|
||||
// };
|
||||
} 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({
|
||||
component: {
|
||||
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
import { createItem, parseGenField } from "lib/gen/utils";
|
||||
import get from "lodash.get";
|
||||
import { generateTableList } from "./gen-table-list";
|
||||
import { generateSelect } from "./md-select";
|
||||
import { on_load } from "./tbl-list/on_load";
|
||||
import { on_submit } from "./tbl-list/on_submit";
|
||||
import { newField } from "./form/fields";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { newField } from "./fields";
|
||||
import { generateSelect } from "../../md/gen/md-select";
|
||||
import { on_load } from "../../md/gen/tbl-list/on_load";
|
||||
import { on_submit } from "../../md/gen/tbl-list/on_submit";
|
||||
|
||||
export const generateForm = async (
|
||||
modify: (data: any) => void,
|
||||
data: any,
|
||||
data: {
|
||||
gen__table: any;
|
||||
gen__fields: any;
|
||||
on_load: any;
|
||||
on_submit: any;
|
||||
body: any;
|
||||
},
|
||||
item: PrasiItem,
|
||||
commit: boolean
|
||||
) => {
|
||||
const table = JSON.parse(data.gen_table.value);
|
||||
console.log("halo");
|
||||
console.log(table);
|
||||
const raw_fields = JSON.parse(data.gen_fields.value) as (
|
||||
const table = JSON.parse(data.gen__table.value);
|
||||
const raw_fields = JSON.parse(data.gen__fields.value) as (
|
||||
| string
|
||||
| { value: string; checked: string[] }
|
||||
)[];
|
||||
let pk = "";
|
||||
console.log({ raw_fields });
|
||||
let pks: Record<string, string> = {};
|
||||
const fields = parseGenField(raw_fields);
|
||||
// convert ke bahasa prisma untuk select
|
||||
const res = generateSelect(fields);
|
||||
pk = res.pk;
|
||||
const select = res.select as any;
|
||||
|
|
@ -33,8 +33,8 @@ export const generateForm = async (
|
|||
alert("Failed to generate! Primary Key not found. ");
|
||||
return;
|
||||
}
|
||||
console.log({ pk, table, select, pks })
|
||||
if (pk) {
|
||||
console.log("masuk");
|
||||
if (data["on_load"]) {
|
||||
result.on_load = {
|
||||
mode: "raw",
|
||||
|
|
@ -49,18 +49,17 @@ export const generateForm = async (
|
|||
}
|
||||
result.body = data["body"];
|
||||
|
||||
console.log({ fields, result });
|
||||
const childs = [];
|
||||
console.log({fields})
|
||||
for (const item of fields.filter((e) => !e.is_pk)) {
|
||||
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;
|
||||
}
|
||||
const field = newField(item, { parent_table: table, value });
|
||||
childs.push(field);
|
||||
}
|
||||
if (commit) {
|
||||
const body = item.edit.childs[0] as PrasiItem;
|
||||
item.edit.setProp("body", {
|
||||
mode: "jsx",
|
||||
value: createItem({
|
||||
|
|
@ -68,9 +67,7 @@ export const generateForm = async (
|
|||
}),
|
||||
});
|
||||
await item.edit.commit();
|
||||
// console.log("done")
|
||||
} 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[];
|
||||
};
|
||||
|
||||
export type GenField = {
|
||||
name: string,
|
||||
is_pk: boolean,
|
||||
type: string,
|
||||
optional: boolean,
|
||||
} | {
|
||||
checked: GenField[],
|
||||
value: GFCol
|
||||
};
|
||||
export type GenField =
|
||||
| {
|
||||
name: string;
|
||||
is_pk: boolean;
|
||||
type: string;
|
||||
optional: boolean;
|
||||
}
|
||||
| {
|
||||
checked: GenField[];
|
||||
value: GFCol;
|
||||
};
|
||||
|
||||
type FieldType =
|
||||
| "-"
|
||||
|
|
@ -56,8 +58,26 @@ export type FieldProp = {
|
|||
width: "auto" | "full" | "¾" | "½" | "⅓" | "¼";
|
||||
_item: PrasiItem;
|
||||
custom?: () => CustomField;
|
||||
on_load: () => any | Promise<any>;
|
||||
on_row: (row: any) => string;
|
||||
on_load: (arg?: any) => any | Promise<any>;
|
||||
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;
|
||||
sub_type: string;
|
||||
placeholder: string;
|
||||
|
|
@ -120,6 +140,9 @@ export type FieldInternal<T extends FieldProp["type"]> = {
|
|||
input: Record<string, any> & {
|
||||
render: () => void;
|
||||
};
|
||||
options: {
|
||||
on_load?: () => Promise<{ value: string; label: string }[]>;
|
||||
};
|
||||
prop?: any;
|
||||
};
|
||||
export type FieldLocal = FieldInternal<any> & {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import { useLocal } from "@/utils/use-local";
|
|||
import { useEffect } from "react";
|
||||
import { FieldInternal, FieldProp } from "../typings";
|
||||
|
||||
export const useField = (arg: Omit<FieldProp, 'name' | 'label'> & {
|
||||
export const useField = (
|
||||
arg: Omit<FieldProp, "name" | "label"> & {
|
||||
name: string | (() => string);
|
||||
label: string | (() => string)
|
||||
}) => {
|
||||
label: string | (() => string);
|
||||
}
|
||||
) => {
|
||||
const field = useLocal<FieldInternal<typeof arg.type>>({
|
||||
status: "init",
|
||||
Child: () => {
|
||||
|
|
@ -14,9 +16,10 @@ export const useField = (arg: Omit<FieldProp, 'name' | 'label'> & {
|
|||
input: {},
|
||||
} as any);
|
||||
|
||||
const name = typeof arg.name === 'string' ? arg.name : arg.name();
|
||||
const label = typeof arg.label === 'string' ? arg.label : arg.label();
|
||||
const required = typeof arg.required === 'string' ? arg.required : arg.required();
|
||||
const name = typeof arg.name === "string" ? arg.name : arg.name();
|
||||
const label = typeof arg.label === "string" ? arg.label : arg.label();
|
||||
const required =
|
||||
typeof arg.required === "string" ? arg.required : arg.required();
|
||||
|
||||
const update_field = {
|
||||
name: name.replace(/\s*/gi, ""),
|
||||
|
|
@ -31,6 +34,7 @@ export const useField = (arg: Omit<FieldProp, 'name' | 'label'> & {
|
|||
required_msg: arg.required_msg,
|
||||
disabled: arg.disabled === "y",
|
||||
};
|
||||
|
||||
if (field.status === "init" || isEditor) {
|
||||
for (const [k, v] of Object.entries(update_field)) {
|
||||
(field as any)[k] = v;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { set } from "lib/utils/set";
|
||||
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 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 (
|
||||
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 { X } from "lucide-react";
|
||||
import { ChevronDown, X } from "lucide-react";
|
||||
import { FC, KeyboardEvent, useCallback, useEffect, useRef } from "react";
|
||||
import { Popover } from "../custom/Popover";
|
||||
import { Badge } from "./badge";
|
||||
import { TypeaheadOptions } from "./typeahead-opt";
|
||||
|
||||
export const Typeahead: FC<{
|
||||
value?: string[];
|
||||
placeholder?: string;
|
||||
options?: (arg: {
|
||||
search: string;
|
||||
existing: { value: string; label: string }[];
|
||||
|
|
@ -16,10 +17,14 @@ export const Typeahead: FC<{
|
|||
search: string;
|
||||
item?: null | { value: string; label: string };
|
||||
}) => string | false;
|
||||
onChange?: (selected: string[]) => void;
|
||||
unique?: boolean;
|
||||
allowNew?: boolean;
|
||||
localSearch?: boolean;
|
||||
autoPopupWidth?: boolean;
|
||||
focusOpen?: boolean;
|
||||
disabled?: boolean;
|
||||
mode?: "multi" | "single";
|
||||
}> = ({
|
||||
value,
|
||||
options: options_fn,
|
||||
|
|
@ -28,6 +33,11 @@ export const Typeahead: FC<{
|
|||
allowNew: allow_new,
|
||||
focusOpen: on_focus_open,
|
||||
localSearch: local_search,
|
||||
autoPopupWidth: auto_popup_width,
|
||||
placeholder,
|
||||
mode,
|
||||
disabled,
|
||||
onChange,
|
||||
}) => {
|
||||
const local = useLocal({
|
||||
value: [] as string[],
|
||||
|
|
@ -42,9 +52,12 @@ export const Typeahead: FC<{
|
|||
result: null as null | { value: string; label: string }[],
|
||||
},
|
||||
unique: typeof unique === "undefined" ? true : unique,
|
||||
allow_new: typeof allow_new === "undefined" ? true : allow_new,
|
||||
on_focus_open: typeof on_focus_open === "undefined" ? false : on_focus_open,
|
||||
allow_new: typeof allow_new === "undefined" ? false : allow_new,
|
||||
on_focus_open: typeof on_focus_open === "undefined" ? true : on_focus_open,
|
||||
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 },
|
||||
});
|
||||
const input = useRef<HTMLInputElement>(null);
|
||||
|
|
@ -55,6 +68,7 @@ export const Typeahead: FC<{
|
|||
options.push({ value: local.search.input, label: local.search.input });
|
||||
}
|
||||
const added = new Set<string>();
|
||||
if (local.mode === "multi") {
|
||||
options = options.filter((e) => {
|
||||
if (!added.has(e.value)) added.add(e.value);
|
||||
else return false;
|
||||
|
|
@ -70,12 +84,15 @@ export const Typeahead: FC<{
|
|||
if (!select_found) {
|
||||
local.select = options[0];
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditor) {
|
||||
if (typeof value === "object" && value) {
|
||||
local.value = value;
|
||||
local.render();
|
||||
}
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const select = useCallback(
|
||||
|
|
@ -110,17 +127,26 @@ export const Typeahead: FC<{
|
|||
if (result) {
|
||||
local.value.push(result);
|
||||
local.render();
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
let val = false as any;
|
||||
if (arg.item) {
|
||||
local.value.push(arg.item.value);
|
||||
val = arg.item.value;
|
||||
} else {
|
||||
if (!arg.search) return false;
|
||||
local.value.push(arg.search);
|
||||
val = arg.search;
|
||||
}
|
||||
|
||||
if (typeof onChange === "function") {
|
||||
onChange(local.value);
|
||||
}
|
||||
local.render();
|
||||
return val;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
|
@ -129,6 +155,14 @@ export const Typeahead: FC<{
|
|||
|
||||
const keydown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!local.open) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
local.open = true;
|
||||
local.render();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Backspace") {
|
||||
if (local.value.length > 0 && e.currentTarget.selectionStart === 0) {
|
||||
local.value.pop();
|
||||
|
|
@ -136,17 +170,32 @@ export const Typeahead: FC<{
|
|||
}
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const selected = select({
|
||||
search: local.search.input,
|
||||
item: local.select,
|
||||
});
|
||||
|
||||
if (selected) {
|
||||
resetSearch();
|
||||
local.render();
|
||||
if (local.mode === "single") {
|
||||
local.open = false;
|
||||
}
|
||||
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) {
|
||||
local.open = true;
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
const idx = options.findIndex((item) => {
|
||||
|
|
@ -170,10 +219,10 @@ export const Typeahead: FC<{
|
|||
if (item.value === local.select?.value) return true;
|
||||
});
|
||||
if (idx >= 0) {
|
||||
if (idx + 1 < options.length) {
|
||||
local.select = options[idx + 1];
|
||||
if (idx - 1 >= 0) {
|
||||
local.select = options[idx - 1];
|
||||
} else {
|
||||
local.select = options[0];
|
||||
local.select = options[options.length - 1];
|
||||
}
|
||||
} else {
|
||||
local.select = options[0];
|
||||
|
|
@ -220,19 +269,34 @@ export const Typeahead: FC<{
|
|||
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 (
|
||||
<div
|
||||
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",
|
||||
css`
|
||||
min-height: 40px;
|
||||
`
|
||||
local.mode === "single" ? "c-cursor-pointer" : "c-cursor-text",
|
||||
"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"
|
||||
)}
|
||||
onClick={() => {
|
||||
input.current?.focus();
|
||||
}}
|
||||
>
|
||||
{local.value.map((e, idx) => {
|
||||
{local.mode === "multi" ? (
|
||||
<>
|
||||
{valueLabel.map((e, idx) => {
|
||||
return (
|
||||
<Badge
|
||||
key={idx}
|
||||
|
|
@ -241,19 +305,23 @@ export const Typeahead: FC<{
|
|||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
local.value = local.value.filter((val) => e !== val);
|
||||
local.value = local.value.filter((val) => e?.value !== val);
|
||||
local.render();
|
||||
input.current?.focus();
|
||||
}}
|
||||
>
|
||||
<div>{e}</div>
|
||||
<div>{e?.label}</div>
|
||||
<X size={12} />
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<WrapOptions
|
||||
wrap={true}
|
||||
<TypeaheadOptions
|
||||
popup={true}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
local.select = null;
|
||||
|
|
@ -267,19 +335,32 @@ export const Typeahead: FC<{
|
|||
onSelect={(value) => {
|
||||
local.open = false;
|
||||
local.value.push(value);
|
||||
|
||||
resetSearch();
|
||||
if (local.mode === "single") {
|
||||
const item = local.options.find((item) => item.value === value);
|
||||
if (item) {
|
||||
local.search.input = item.label;
|
||||
}
|
||||
}
|
||||
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
|
||||
placeholder={local.mode === "multi" ? placeholder : ""}
|
||||
type="text"
|
||||
ref={input}
|
||||
value={local.search.input}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
|
||||
if (!local.open) {
|
||||
if (local.on_focus_open) {
|
||||
openOptions();
|
||||
|
|
@ -313,6 +394,15 @@ export const Typeahead: FC<{
|
|||
local.search.result = local.options.filter((e) =>
|
||||
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 {
|
||||
local.search.result = null;
|
||||
}
|
||||
|
|
@ -335,7 +425,6 @@ export const Typeahead: FC<{
|
|||
});
|
||||
local.search.searching = false;
|
||||
local.search.promise = null;
|
||||
local.render();
|
||||
} else {
|
||||
local.search.result = result.map((item) => {
|
||||
if (typeof item === "string")
|
||||
|
|
@ -343,95 +432,43 @@ export const Typeahead: FC<{
|
|||
return item;
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
}}
|
||||
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}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
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 */
|
||||
export { generateMasterDetail } from "lib/comps/md/gen/md-gen";
|
||||
export { generateMasterDetail } from "@/comps/md/gen/md-gen";
|
||||
|
||||
/** ETC */
|
||||
export { filterWhere } from "@/comps/filter/utils/filter-where";
|
||||
|
|
@ -55,12 +55,12 @@ export {
|
|||
export { MasterDetailType } from "@/comps/md/utils/typings";
|
||||
export { FormatValue } from "@/utils/format-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 { prasi_gen } from "@/gen/prasi_gen";
|
||||
export { password } from "@/utils/password";
|
||||
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 */
|
||||
export {
|
||||
|
|
@ -97,7 +97,7 @@ export { Profile } from "@/preset/profile/Profile";
|
|||
export { generateProfile } from "@/preset/profile/utils/generate";
|
||||
export { ButtonUpload } from "@/preset/profile/ButtonUpload";
|
||||
export { longDate, shortDate, timeAgo, formatTime } from "@/utils/date";
|
||||
|
||||
export { getPathname } from "./utils/pathname";
|
||||
|
||||
export * from '@/comps/ui/typeahead'
|
||||
export * from '@/comps/ui/input'
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import set from "lodash.set";
|
||||
import { set } from "lib/utils/set";
|
||||
|
||||
const cache: any = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ export type RGSession = {
|
|||
};
|
||||
|
||||
export const logout = (url_login?: string) => {
|
||||
console.log("halo")
|
||||
if (typeof get(w, "user") === "object") {
|
||||
w.user = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import set from "lodash.set";
|
||||
import { set } from "lib/utils/set";
|
||||
|
||||
export const select = (rel: any) => {
|
||||
const result = {};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { select } from "@/preset/login/utils/select";
|
||||
import { set } from "lib/utils/set";
|
||||
import get from "lodash.get";
|
||||
import set from "lodash.set";
|
||||
|
||||
type typeFieldLogin = {
|
||||
upload: string;
|
||||
|
|
@ -29,7 +29,7 @@ export const generateProfile = async (
|
|||
`,
|
||||
});
|
||||
}
|
||||
console.log({btn})
|
||||
console.log({ btn });
|
||||
if (btn) {
|
||||
const upload = btn.edit.childs.find(
|
||||
(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[]
|
||||
): {
|
||||
[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 _ = useRef({
|
||||
data: data as unknown as T & {
|
||||
render: (force?: boolean) => void;
|
||||
render: () => void;
|
||||
},
|
||||
deps: (deps || []) as any[],
|
||||
ready: false,
|
||||
_loading: {} as any,
|
||||
lastRender: 0,
|
||||
lastRenderCount: 0,
|
||||
});
|
||||
const local = _.current;
|
||||
|
||||
|
|
@ -25,9 +28,23 @@ export const useLocal = <T extends object>(
|
|||
}, []);
|
||||
|
||||
if (local.ready === false) {
|
||||
local.data.render = (force) => {
|
||||
if (force) _render({});
|
||||
else if (local.ready) _render({});
|
||||
local._loading = {};
|
||||
|
||||
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 {
|
||||
if (local.deps.length > 0 && deps) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue