This commit is contained in:
Rizky 2024-07-01 21:19:47 -07:00
parent 82f625bf38
commit edb153e260
18 changed files with 215 additions and 53 deletions

View File

@ -187,6 +187,8 @@ export function Popover({
content?: React.ReactNode;
arrow?: boolean;
} & PopoverOptions) {
if (isEditor) return children;
const popover = usePopover({ modal, ...restOptions });
let _content = content;

View File

@ -36,6 +36,7 @@ export const FieldInput: FC<{
const errors = fm.error.get(name);
let type_field: any = typeof arg.type === "function" ? arg.type() : arg.type; // tipe field
const disabled = typeof field.disabled === "function" ? field.disabled() : field.disabled;
let custom = <></>;
if (field.type === "custom") {
let res = arg.custom?.() || <></>;
@ -113,7 +114,7 @@ export const FieldInput: FC<{
? css`
border-color: transparent;
`
: field.disabled
: disabled
? "c-border-gray-100"
: errors.length > 0
? field.focused
@ -145,7 +146,7 @@ export const FieldInput: FC<{
"field-inner c-flex-1 c-flex c-items-center",
field.type === "link" && "c-justify-end",
field.focused && "focused",
field.disabled && "c-pointer-events-none"
disabled && "c-pointer-events-none"
)}
>
{not_ready ? (

View File

@ -46,7 +46,6 @@ export const TypeDropdown: FC<{
}
if (
field.type === "single-option" &&
!value &&
field.required &&
local.options.length > 0
) {
@ -104,6 +103,7 @@ export const TypeDropdown: FC<{
);
}
}
const disabled = typeof field.disabled === "function" ? field.disabled() : field.disabled;
if (!local.loaded) return <FieldLoading />;
if (field.type === "single-option") {
@ -129,7 +129,7 @@ export const TypeDropdown: FC<{
return item?.value || search;
}}
disabled={field.disabled}
disabled={disabled}
allowNew={false}
autoPopupWidth={true}
focusOpen={true}
@ -165,7 +165,7 @@ export const TypeDropdown: FC<{
autoPopupWidth={true}
focusOpen={true}
mode={"multi"}
disabled={field.disabled}
disabled={disabled}
placeholder={arg.placeholder}
options={() => {
return local.options;

View File

@ -96,6 +96,7 @@ export const FieldTypeInput: FC<{
input.change_timeout = setTimeout(fm.render, 300);
};
const disabled = typeof field.disabled === "function" ? field.disabled() : field.disabled;
switch (type_field) {
case "toggle":
return (
@ -144,7 +145,7 @@ export const FieldTypeInput: FC<{
renderOnChange();
}}
value={value || ""}
disabled={field.disabled}
disabled={disabled}
className="c-flex-1 c-bg-transparent c-outline-none c-p-2 c-text-sm c-w-full"
spellCheck={false}
onFocus={() => {
@ -175,7 +176,7 @@ export const FieldTypeInput: FC<{
return (
<Datepicker
value={{ startDate: value, endDate: value }}
disabled={field.disabled}
disabled={disabled}
displayFormat="DD MMM YYYY"
asSingle={true}
useRange={false}
@ -215,7 +216,7 @@ export const FieldTypeInput: FC<{
}}
placeholder={prop.placeholder || arg.placeholder || ""}
value={value}
disabled={field.disabled}
disabled={disabled}
className="c-flex-1 c-transition-all c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
spellCheck={false}
onFocus={(e) => {

View File

@ -1,5 +1,5 @@
import { useLocal } from "@/utils/use-local";
import { FC } from "react";
import { FC, useEffect } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { PropTypeInput } from "./TypeInput";
import { isEmptyString } from "lib/utils/is-empty-string";
@ -16,11 +16,17 @@ export const FieldMoney: FC<{
display: false as any,
ref: null as any,
});
useEffect(() => {
input.value = value;
input.render();
}, [fm.data[field.name]]);
let display: any = null;
const disabled =
typeof field.disabled === "function" ? field.disabled() : field.disabled;
const money = formatMoney(Number(value) || 0);
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
<div
{/* <div
className={cx(
input.display ? "c-hidden" : "",
"c-flex-grow c-px-2 c-flex c-flex-row c-items-center",
@ -35,26 +41,47 @@ export const FieldMoney: FC<{
}}
>
{isEmptyString(value) ? arg.placeholder : money}
</div>
</div> */}
<input
ref={(el) => (input.ref = el)}
type={"number"}
type={"text"}
onClick={() => {}}
onChange={(ev) => {
fm.data[field.name] = Number(ev.currentTarget.value);
const rawValue = ev.currentTarget.value
.replace(/[^0-9,-]/g, "")
.toString();
const now = Number(value) || 0;
if (
!rawValue.endsWith(",") &&
!rawValue.endsWith("-") &&
convertionCurrencyNumber(rawValue) !==
convertionCurrencyNumber(input.value)
) {
fm.data[field.name] = convertionCurrencyNumber(
formatCurrency(rawValue)
);
fm.render();
if (field.on_change) {
field.on_change({
value: Number(fm.data[field.name]),
value: convertionCurrencyNumber(
formatCurrency(fm.data[field.name])
),
name: field.name,
fm,
});
}
input.value = formatCurrency(fm.data[field.name]);
input.render();
} else {
input.value = rawValue;
input.render();
}
}}
value={value}
disabled={field.disabled}
value={formatCurrency(input.value)}
disabled={disabled}
className={cx(
!input.display ? "c-hidden" : "",
// !input.display ? "c-hidden" : "",
"c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
)}
spellCheck={false}
@ -62,8 +89,8 @@ export const FieldMoney: FC<{
field.focused = true;
field.render();
}}
placeholder={arg.placeholder || ""}
onBlur={() => {
console.log("blur");
field.focused = false;
input.display = !input.display;
input.render();
@ -73,9 +100,91 @@ export const FieldMoney: FC<{
</div>
);
};
const convertionCurrencyNumber = (value: string) => {
if (!value) return null;
let numberString = value.toString().replace(/[^0-9,-]/g, "");
if (numberString.endsWith(",")) {
return Number(numberString.replace(",", "")) || 0;
}
if (numberString.endsWith("-")) {
return Number(numberString.replace("-", "")) || 0;
}
const rawValue = numberString.replace(/[^0-9,-]/g, "").replace(",", ".");
return parseFloat(rawValue) || 0;
return Number(numberString) || 0;
};
const formatCurrency = (value: any) => {
// Menghapus semua karakter kecuali angka, koma, dan tanda minusif (value === null || value === undefined) return '';
if (!value) return "";
let numberString = "";
if (typeof value === "number") {
numberString = formatMoney(value);
} else {
numberString = value.toString().replace(/[^0-9,-]/g, "");
}
if (numberString.endsWith("-") && numberString.startsWith("-")) {
return "-";
} else if (numberString.endsWith(",")) {
const isNegative = numberString.startsWith("-");
numberString = numberString.replace("-", "");
const split = numberString.split(",");
if (isNumberOrCurrency(split[0]) === "Number") {
split[0] = formatMoney(Number(split[0]));
}
let rupiah = split[0];
rupiah = split[1] !== undefined ? rupiah + "," + split[1] : rupiah;
return (isNegative ? "-" : "") + rupiah;
} else {
const isNegative = numberString.startsWith("-");
numberString = numberString.replace("-", "");
const split = numberString.split(",");
if (isNumberOrCurrency(split[0]) === "Number") {
split[0] = formatMoney(Number(split[0]));
}
let rupiah = split[0];
rupiah = split[1] !== undefined ? rupiah + "," + split[1] : rupiah;
return (isNegative ? "-" : "") + rupiah;
}
};
export const formatMoney = (res: number) => {
const formattedAmount = new Intl.NumberFormat("id-ID", {
minimumFractionDigits: 0,
}).format(res);
return formattedAmount;
};
const isNumberOrCurrency = (input: any) => {
// Pengecekan apakah input adalah angka biasa
if (typeof input === "string") {
let rs = input;
if (input.startsWith("-")) {
rs = rs.replace("-", "");
}
const dots = rs.match(/\./g);
if (dots && dots.length > 1) {
return "Currency";
} else if (dots && dots.length === 1) {
if (!hasNonZeroDigitAfterDecimal(rs)) {
return "Currency";
} else {
return "Number";
}
}
}
if (!isNaN(input)) {
return "Number";
}
// Pengecekan apakah input adalah format mata uang dengan pemisah ribuan
const currencyRegex = /^-?Rp?\s?\d{1,3}(\.\d{3})*$/;
if (currencyRegex.test(input)) {
return "Currency";
}
// Jika tidak terdeteksi sebagai angka atau format mata uang, kembalikan null atau sesuai kebutuhan
return null;
};
const hasNonZeroDigitAfterDecimal = (input: string) => {
// Ekspresi reguler untuk mencocokkan angka 1-9 setelah koma atau titik
const regex = /[.,]\d*[1-9]\d*/;
return regex.test(input);
};

View File

@ -21,6 +21,7 @@ export const FieldUpload: FC<{
drop: false as boolean,
});
let display: any = null;
const disabled = typeof field.disabled === "function" ? field.disabled() : field.disabled;
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
<div
@ -152,7 +153,7 @@ export const FieldUpload: FC<{
fm.render();
}}
value={value}
disabled={field.disabled}
disabled={disabled}
className={cx(
!input.display ? "c-hidden" : "",
"c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"

View File

@ -105,6 +105,37 @@ export const newField = async (
} else if (["has-many", "has-one"].includes(arg.type) && arg.relation) {
const fields = parseGenField(opt.value);
const res = generateSelect(fields);
if (res && res.select && Object.keys(res.select).length === 1) {
return createItem({
component: {
id: "32550d01-42a3-4b15-a04a-2c2d5c3c8e67",
props: {
name: arg.name,
label: formatName(arg.name),
type: "link",
link_opt: [
`({
url: () => {
return "";
},
where: () => {
return {} as ${
opt.parent_table
? `Prisma.${opt.parent_table}WhereInput`
: `Record<string, any>`
};
},
breadcrumbs: (existing: any[]) => {
return [...existing];
},
})`,
],
},
},
});
}
const load = on_load_rel({
pk: res.pk,
table: arg.relation.to.table,

View File

@ -37,9 +37,11 @@ export const generateForm = async (
return;
}
if (pk) {
const is_md =
let is_md: boolean | string =
item.edit?.parent?.item?.component?.id ===
"cb52075a-14ab-455a-9847-6f1d929a2a73";
if (!is_md) is_md = "";
if (data["on_load"]) {
result.on_load = {
mode: "raw",
@ -56,8 +58,9 @@ export const generateForm = async (
md.render();
}
`,
is_md: true,
}
: {},
: { is_md },
}),
};
}
@ -67,17 +70,14 @@ export const generateForm = async (
value: `\
async ({ form, error, fm }: IForm) => {
let result = false;
try {
${
try {${
is_md &&
`\
if (typeof md !== "undefined") {
fm.status = "saving";
md.render();
}`
}
}
const data = { ...form };
const record = {} as Record<string, any>;

View File

@ -14,6 +14,7 @@ export const on_load = ({
opt?: {
before_load?: string;
after_load?: string;
is_md?: boolean | string;
};
}) => {
const sample: any = {};
@ -29,18 +30,26 @@ export const on_load = ({
}
}
let is_md: string | boolean =
typeof opt?.is_md === "undefined" ? true : !!opt?.is_md;
if (!is_md) is_md = "";
return `\
async (opt) => {
if (isEditor) return ${JSON.stringify(sample)};
if (isEditor) return ${JSON.stringify(sample, null, 2)};
let raw_id = params.id;
${
is_md &&
`\
if (typeof md === 'object' && md.selected && md.pk) {
const pk = md.pk?.name;
if (md.selected[pk]) {
raw_id = md.selected[pk];
}
}
`
}
${opt?.before_load ? opt.before_load : `let id = raw_id`}
let item = {};
if (id){
@ -63,9 +72,7 @@ async (opt) => {
where,
select: gen.select,
});
${opt?.after_load ? opt?.after_load : ""}
return item;
} else {
${opt?.after_load ? opt?.after_load : ""}

View File

@ -29,7 +29,6 @@ export const on_load_rel = ({
!isEmptyString(type) &&
["checkbox", "typeahead", "button"].includes(type as any);
console.log(skip_select, type);
return `\
async (arg: {
reload: () => Promise<void>;

View File

@ -52,7 +52,7 @@ export type FieldProp = {
required_msg: (name: string) => string | ReactElement;
on_change: (arg: { value: any }) => void | Promise<void>;
PassProp: any;
disabled: "y" | "n";
disabled: ("y" | "n") | (() => true | false);
child: any;
selection: "single" | "multi";
prefix: any;
@ -152,7 +152,7 @@ export type FieldInternal<T extends FieldProp["type"]> = {
width: FieldProp["width"];
required: boolean;
focused: boolean;
disabled: boolean;
disabled: boolean | (() => boolean);
required_msg: FieldProp["required_msg"];
col?: GFCol;
ref?: any;

View File

@ -23,7 +23,6 @@ export const useField = (
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, ""),
label: label,
@ -35,7 +34,7 @@ export const useField = (
custom: arg.custom,
required: required === "y",
required_msg: arg.required_msg,
disabled: arg.disabled === "y",
disabled: typeof arg.disabled === "function" ? arg.disabled : arg.disabled === "y",
on_change: arg.on_change,
};

View File

@ -399,7 +399,7 @@ export const TableList: FC<TableListProp> = ({
renderHeaderCell(props) {
return (
<div>
<CheckboxList value={false} on_click={on_click} />
{/* <CheckboxList value={false} on_click={on_click} /> */}
</div>
);
},

View File

@ -1,14 +1,13 @@
import { getPathname } from "lib/utils/pathname";
import { useLocal } from "lib/utils/use-local";
import get from "lodash.get";
import { FC } from "react";
import { FC, useRef } from "react";
import { IMenu, MenuProp } from "../../preset/menu/utils/type-menu";
export const Menu: FC<MenuProp> = (props) => {
const imenu = props.menu[0];
let role = props.role;
role = props.on_init() as string;
const PassProp = props.PassProp;
let menu = imenu[role] || [];
const pathname = getPathname();
@ -26,8 +25,10 @@ export const Menu: FC<MenuProp> = (props) => {
local.render();
}
}
const ref = useRef<HTMLDivElement>(null);
return (
<div
ref={ref}
className={cx(
props.mode === "mini" ? "c-max-w-[35px]" : "",
"c-h-full c-w-full c-flex c-flex-row c-flex-grow c-px-3 c-py-4 c-overflow-y-auto c-rounded "

View File

@ -35,7 +35,7 @@ export const Popup: FC<PopupProp> = ({ on_close, open, child }) => {
createPortal(
<div
ref={(e) => (local.ref = e)}
className="c-w-screen c-h-screen c-bg-transparent c-flex c-flex-row c-items-center c-justify-center"
className="c-w-screen c-h-screen relative c-bg-transparent c-flex c-flex-row c-items-center c-justify-center"
onClick={(e) => {
if (local.ref) {
if (e.target === local.ref) {

View File

@ -1,5 +1,6 @@
export { FieldLoading } from "@/comps/ui/field-loading";
import { lazify, lazifyMany } from "@/utils/lazify";
export { Popover } from "./comps/custom/Popover";
/** Master - Detail - List - Form */
export const MasterDetail = lazify(
@ -80,7 +81,7 @@ export { FormatValue } from "@/utils/format-value";
export { GetValue } from "@/utils/get-value";
export { password } from "@/utils/password";
export { prasi_events, call_prasi_events } from "lib/utils/prasi-events";
export { getFilter } from "@/comps/filter/utils/get-filter";
/** Session */
export { Login } from "@/preset/login/Login";
export { generateLogin } from "@/preset/login/utils/generate";

View File

@ -1,7 +1,7 @@
import { getPathname } from "lib/utils/pathname";
import { useLocal } from "lib/utils/use-local";
import get from "lodash.get";
import { FC, useEffect } from "react";
import { FC, useEffect, useRef } from "react";
import { IMenu, MenuProp } from "./utils/type-menu";
// import { icon } from "../../..";
@ -22,7 +22,7 @@ export const Menu: FC<MenuProp> = (props) => {
let role = props.role;
role = props.on_init();
let menu = get(imenu, role) || [];
const ref = useRef<HTMLDivElement>(null);
const local = useLocal({ ...local_default });
if (local.pathname !== getPathname()) {
@ -39,7 +39,10 @@ export const Menu: FC<MenuProp> = (props) => {
}, [props.mode]);
return (
<div className={cx("c-overflow-y-auto c-relative c-h-full c-w-full ")}>
<div
className={cx("c-overflow-y-auto c-relative c-h-full c-w-full ")}
ref={ref}
>
<div className="sidebar-menu c-absolute c-inset-0 c-flex c-flex-col c-flex-grow c-px-3 c-py-4 ">
<SideBar
data={menu}

View File

@ -12,7 +12,7 @@ export const FormatValue: FC<{
name: string;
gen_fields: string[];
tree_depth?: number;
mode?: "money" | "datetime" | "timeago";
mode?: "money" | "datetime" | "timeago" | "date";
}> = (prop) => {
const { value, gen_fields, name, tree_depth, mode } = prop;
if (gen_fields) {
@ -54,7 +54,14 @@ export const FormatValue: FC<{
} catch (ex: any) {
return "-";
}
} else if (mode === "timeago") {
} else if (mode === "date") {
if (!value || isEmptyString(value)) return "-";
try {
return formatDate(dayjs(value), "DD MMMM YYYY");
} catch (ex: any) {
return "-";
}
}else if (mode === "timeago") {
if (!value || isEmptyString(value)) return "-";
try {
return timeAgo(dayjs(value));