Compare commits

..

10 Commits

Author SHA1 Message Date
rizky f56494bca5 fix 2025-03-17 07:08:07 +07:00
rizky 1402297f57 fix 2025-03-11 03:31:51 -07:00
rizrmd 5901e32479 checkpoint 2025-02-19 13:28:04 +00:00
Rizky 09a819dcf1 fixing 2024-12-11 10:15:24 +00:00
Rizky 99e5baeefb fix 2024-12-11 09:27:29 +00:00
Rizky 0d7c142e93 fix search filter 2024-12-11 09:13:10 +00:00
Rizky c0e612e6f6 fix lib 2024-12-11 04:41:06 +00:00
Rizky 59495ee186 fix 2024-12-10 03:01:57 +00:00
Rizky ec73db3925 fix 2024-12-02 20:09:26 +07:00
Rizky 7d85e3d52e add input otp 2024-12-02 18:01:40 +07:00
22 changed files with 791 additions and 706 deletions

View File

@ -13,7 +13,7 @@ export const FilterContent: FC<{
child: any; child: any;
_item: PrasiItem; _item: PrasiItem;
}> = ({ mode, filter, PassProp, child, _item, onSubmit }) => { }> = ({ mode, filter, PassProp, child, _item, onSubmit }) => {
const internal = useLocal({}); filter.PassProp = PassProp;
return ( return (
<div <div
className={cx( className={cx(
@ -94,7 +94,6 @@ export const FilterContent: FC<{
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 1000; z-index: 1000;
/* Styles specific to popup */
} }
.form-inner { .form-inner {
@ -104,61 +103,100 @@ export const FilterContent: FC<{
)} )}
> >
<BaseForm <BaseForm
tag="blank"
data={filter.data} data={filter.data}
onSubmit={async (form) => { name={filter.name}
const fm = form.fm; onSubmit={async ({ fm }) => {
try { try {
if (typeof form.fm?.data === "object") { if (typeof fm.data === "object") {
form.render(); fm.render();
form.fm.render(); }
if (mode === "raw" && fm) {
const submit = async (fm: FMLocal) => {
fm.render();
if (typeof onSubmit === "function") {
const data = await onSubmit(fm);
if (typeof fm.data === "object") {
fm.data = {
__status: "submit",
...fm.data,
_where: data,
};
fm.render();
filter.data = {
__status: "submit",
...fm.data,
_where: data,
};
filter.render();
}
}
};
await submit(fm);
}
const f = getFilter(filter.name);
if (f) {
for (const list of Object.values(f.list.ref)) {
list.reload();
}
} }
} catch (ex) {} } catch (ex) {}
return true;
if (mode === "raw" && fm) {
if (form && form.fm) {
Object.keys(form.fm.data).map((e) => {
if (!form?.fm.data?.[e]) {
delete form.fm?.data[e];
}
});
}
const submit = async (fm: FMLocal) => {
fm.render();
if (typeof onSubmit === "function") {
const data = await onSubmit(fm);
if (typeof form.fm?.data === "object") {
form.fm.data = {
__status: "submit",
...form.fm.data,
_where: data,
};
form.fm.render();
filter.data = {
__status: "submit",
...form.fm.data,
_where: data,
};
filter.render();
}
}
};
await submit(fm);
}
const f = getFilter(filter.name);
if (f) {
for (const list of Object.values(f.list.ref)) {
list.reload();
}
}
}} }}
render={internal.render} // onSubmit={async (form) => {
// const fm = form.fm;
// try {
// if (typeof form.fm?.data === "object") {
// form.render();
// form.fm.render();
// }
// } catch (ex) {}
// if (mode === "raw" && fm) {
// if (form && form.fm) {
// Object.keys(form.fm.data).map((e) => {
// if (!form?.fm.data?.[e]) {
// delete form.fm?.data[e];
// }
// });
// }
// const submit = async (fm: FMLocal) => {
// fm.render();
// if (typeof onSubmit === "function") {
// const data = await onSubmit(fm);
// if (typeof form.fm?.data === "object") {
// form.fm.data = {
// __status: "submit",
// ...form.fm.data,
// _where: data,
// };
// form.fm.render();
// filter.data = {
// __status: "submit",
// ...form.fm.data,
// _where: data,
// };
// filter.render();
// }
// }
// };
// await submit(fm);
// }
// const f = getFilter(filter.name);
// if (f) {
// for (const list of Object.values(f.list.ref)) {
// list.reload();
// }
// }
// }}
> >
{(form) => { {({ fm }) => {
filter.form = form; filter.fm = fm;
return ( return (
<> <>
{!!(PassProp && child) && ( {!!(PassProp && child) && (
<PassProp filter={filter} fm={form.fm}> <PassProp filter={filter} fm={filter.fm}>
{child} {child}
</PassProp> </PassProp>
)} )}

View File

@ -20,8 +20,9 @@ export const FilterField: FC<{
search_timeout: null as any, search_timeout: null as any,
}); });
if (!name) return <>No Name</>; if (!name) return <>No Name</>;
if (!filter.form) return <div>Loading...</div>; if (!filter.fm) return <div>Loading...</div>;
const fm = filter.fm;
filter.types[name] = type; filter.types[name] = type;
const singleOptions = ["equal", "not_equal"]; const singleOptions = ["equal", "not_equal"];
@ -32,167 +33,183 @@ export const FilterField: FC<{
internal.render_timeout = setTimeout(() => { internal.render_timeout = setTimeout(() => {
filter_window.prasiContext.render(); filter_window.prasiContext.render();
}, 500); }, 500);
}, [filter.form]); }, [fm]);
let show_modifier = filter.mode !== "inline"; let show_modifier = filter.mode !== "inline";
return null; const arg = {
// return ( name: name || "",
// <BaseField fm,
// {...filter.form.fieldProps({ label: label || name || "",
// name: name || "", render: internal.render,
// label: label || name || "", prefix: show_modifier
// render: internal.render, ? () => (
// prefix: show_modifier <FieldModifier
// ? () => ( onChange={(modifier) => {
// <FieldModifier filter.modifiers[name] = modifier;
// onChange={(modifier) => { filter.render();
// filter.modifiers[name] = modifier; filter_window.prasiContext.render();
// filter.render(); }}
// filter_window.prasiContext.render(); modifier={filter.modifiers[name]}
// }} type={type}
// modifier={filter.modifiers[name]} />
// type={type} )
// /> : undefined,
// ) onLoad() {
// : undefined, return [{ label: "halo", value: "asda" }];
// onLoad() { },
// return [{ label: "halo", value: "asda" }]; subType: singleOptions.includes(filter.modifiers[name])
// }, ? "dropdown"
// subType: singleOptions.includes(filter.modifiers[name]) : "typeahead",
// ? "dropdown" };
// : "typeahead",
// })}
// >
// {(field) => {
// if (type === "search-all") {
// return (
// <div className={cx("search-all c-flex items-center")}>
// <div className="c-pl-2">
// <svg
// xmlns="http://www.w3.org/2000/svg"
// width="14"
// height="14"
// viewBox="0 0 24 24"
// fill="none"
// stroke="currentColor"
// strokeWidth="2"
// strokeLinecap="round"
// strokeLinejoin="round"
// >
// <circle cx="11" cy="11" r="8" />
// <path d="m21 21-4.3-4.3" />
// </svg>
// </div>
// <input
// type="search"
// value={field.fm?.data?.[name]}
// placeholder={field.field.label}
// onBlur={() => {
// // clearTimeout(internal.search_timeout);
// // filter.form?.submit();
// }}
// spellCheck={false}
// className="c-flex-1 c-transition-all c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
// onChange={(e) => {
// field.fm.data[name] = e.currentTarget.value;
// field.fm.render();
// clearTimeout(internal.search_timeout);
// internal.search_timeout = setTimeout(() => {
// filter.form?.submit();
// }, 1500);
// }}
// />
// </div>
// );
// }
// return ( return (
// <> <BaseField
// {type === "text" && ( fm={fm}
// <FieldTypeInput label={label || name}
// {...field} name={name || ""}
// prop={{ PassProp={filter.PassProp}
// type: "input", >
// sub_type: "text", {(field) => {
// }} if (type === "search-all") {
// /> return (
// )} <div className={cx("search-all c-flex items-center")}>
// {type === "number" && ( <div className="c-pl-2">
// <> <svg
// <FieldTypeInput xmlns="http://www.w3.org/2000/svg"
// {...field} width="14"
// field={{ height="14"
// ...field.field, viewBox="0 0 24 24"
// name: fill="none"
// filter.modifiers[name] === "between" stroke="currentColor"
// ? name strokeWidth="2"
// : `${name}_from`, strokeLinecap="round"
// }} strokeLinejoin="round"
// prop={{ >
// type: "input", <circle cx="11" cy="11" r="8" />
// sub_type: "number", <path d="m21 21-4.3-4.3" />
// }} </svg>
// /> </div>
// {filter.modifiers[name] === "between" && ( <input
// <FieldTypeInput type="search"
// {...field} value={field.fm?.data?.[name]}
// field={{ ...field.field, name: `${name}_to` }} placeholder={field.field.label}
// prop={{ onBlur={() => {
// type: "input", // clearTimeout(internal.search_timeout);
// sub_type: "number", // filter.form?.submit();
// }} }}
// /> spellCheck={false}
// )} className="c-flex-1 c-transition-all c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
// </> onChange={(e) => {
// )} field.fm.data[name] = e.currentTarget.value;
// {type === "date" && ( field.fm.render();
// <> clearTimeout(internal.search_timeout);
// <FieldTypeInput if (!field.fm.data[name]) {
// {...field} fm.submit();
// field={{ } else {
// ...field.field, internal.search_timeout = setTimeout(() => {
// name: fm?.submit();
// filter.modifiers[name] === "between" }, 1500);
// ? name }
// : `${name}_from`, }}
// }} onKeyDown={(e) => {
// prop={{ if (e.key === "Enter") {
// type: "input", clearTimeout(internal.search_timeout);
// sub_type: "date", fm?.submit();
// }} }
// /> }}
// {filter.modifiers[name] === "between" && ( />
// <FieldTypeInput </div>
// {...field} );
// field={{ ...field.field, name: `${name}_to` }} }
// prop={{
// type: "input", return (
// sub_type: "date", <>
// }} {type === "text" && (
// /> <FieldTypeInput
// )} {...field}
// </> arg={arg}
// )} prop={{
// {type === "boolean" && ( type: "input",
// <FieldCheckbox sub_type: "text",
// arg={field.arg} }}
// field={field.field} />
// fm={field.fm} )}
// /> {type === "number" && (
// )} <>
// {type === "options" && ( <FieldTypeInput
// <> arg={arg}
// {singleOptions.includes(filter.modifiers[name]) && ( {...field}
// <SingleOption {...field} /> field={{
// )} ...field.field,
// {multiOptions.includes(filter.modifiers[name]) && ( name:
// <MultiOption {...field} /> filter.modifiers[name] === "between"
// )} ? name
// </> : `${name}_from`,
// )} }}
// </> prop={{
// ); type: "input",
// }} sub_type: "number",
// </BaseField> }}
// ); />
{filter.modifiers[name] === "between" && (
<FieldTypeInput
arg={arg}
{...field}
field={{ ...field.field, name: `${name}_to` }}
prop={{
type: "input",
sub_type: "number",
}}
/>
)}
</>
)}
{type === "date" && (
<>
<FieldTypeInput
arg={arg}
{...field}
field={{
...field.field,
name:
filter.modifiers[name] === "between"
? name
: `${name}_from`,
}}
prop={{
type: "input",
sub_type: "date",
}}
/>
{filter.modifiers[name] === "between" && (
<FieldTypeInput
arg={arg}
{...field}
field={{ ...field.field, name: `${name}_to` }}
prop={{
type: "input",
sub_type: "date",
}}
/>
)}
</>
)}
{type === "boolean" && (
<FieldCheckbox arg={arg} field={field.field} fm={field.fm} />
)}
{type === "options" && (
<>
{singleOptions.includes(filter.modifiers[name]) && (
<SingleOption arg={arg} {...field} />
)}
{multiOptions.includes(filter.modifiers[name]) && (
<MultiOption arg={arg} {...field} />
)}
</>
)}
</>
);
}}
</BaseField>
);
}; };

View File

@ -73,7 +73,6 @@ export const MasterFilter: FC<FilterProps> = ({
} }
filter.raw_status = "ready"; filter.raw_status = "ready";
filter.render(); filter.render();
}); });
} else { } else {
filter.raw_status = "ready"; filter.raw_status = "ready";
@ -95,6 +94,7 @@ export const MasterFilter: FC<FilterProps> = ({
if (mode === "raw" && filter.raw_status !== "ready") { if (mode === "raw" && filter.raw_status !== "ready") {
return <FieldLoading />; return <FieldLoading />;
} }
return ( return (
<> <>
<FilterContent <FilterContent

View File

@ -1,5 +1,4 @@
import { BaseFormLocal } from "../../form/base/types"; import { FMLocal, GenField } from "../../form/typings";
import { GenField } from "../../form/typings";
export type FilterFieldType = export type FilterFieldType =
| "search-all" | "search-all"
@ -14,7 +13,8 @@ export const default_filter_local = {
columns: [] as string[], columns: [] as string[],
fields: [] as GenField[], fields: [] as GenField[],
tableName: "", tableName: "",
form: null as null | BaseFormLocal<any>, fm: null as null | FMLocal,
PassProp: null as null | any,
modifiers: {} as Record<string, string>, modifiers: {} as Record<string, string>,
types: {} as Record<string, FilterFieldType>, types: {} as Record<string, FilterFieldType>,
name: "", name: "",

View File

@ -43,6 +43,7 @@ export const BaseForm = <T extends Record<string, any>>({
}, },
}); });
} }
useEffect(() => { useEffect(() => {
if (local.fm && local.fm.data !== data) { if (local.fm && local.fm.data !== data) {
for (const k of Object.keys(local.fm.data)) { for (const k of Object.keys(local.fm.data)) {
@ -53,6 +54,9 @@ export const BaseForm = <T extends Record<string, any>>({
} }
local.fm.render(); local.fm.render();
} }
return () => {
delete (local as any).fm;
};
}, [data]); }, [data]);
const fm = local.fm; const fm = local.fm;

View File

@ -26,11 +26,11 @@ export const TypeDropdown: FC<{
if (Array.isArray(res)) { if (Array.isArray(res)) {
const list: any = res.map((e: any, idx: number) => { const list: any = res.map((e: any, idx: number) => {
return { return {
label: arg.opt_get_label(e, "list", { label: arg.opt_get_label?.(e, "list", {
prev: res[idx - 1], prev: res[idx - 1],
next: res[idx + 1], next: res[idx + 1],
}), }),
tag: arg.opt_get_label(e, "label"), tag: arg.opt_get_label?.(e, "label"),
value: e.value, value: e.value,
data: e.data, data: e.data,
}; };
@ -48,7 +48,7 @@ export const TypeDropdown: FC<{
let f = list.find((ex: any) => ex.value === v); let f = list.find((ex: any) => ex.value === v);
if (!f) { if (!f) {
arg.opt_set_value({ arg.opt_set_value?.({
fm, fm,
name: field.name, name: field.name,
type: field.type, type: field.type,
@ -75,7 +75,7 @@ export const TypeDropdown: FC<{
if (field.type === "single-option") { if (field.type === "single-option") {
if (!value && local.options.length > 0) { if (!value && local.options.length > 0) {
arg.opt_set_value({ arg.opt_set_value?.({
fm, fm,
name: field.name, name: field.name,
type: field.type, type: field.type,
@ -83,7 +83,7 @@ export const TypeDropdown: FC<{
selected: [local.options[0]?.value], selected: [local.options[0]?.value],
}); });
} else if (value) { } else if (value) {
arg.opt_set_value({ arg.opt_set_value?.({
fm, fm,
name: field.name, name: field.name,
type: field.type, type: field.type,
@ -191,7 +191,7 @@ export const TypeDropdown: FC<{
popupClassName={popupClassName} popupClassName={popupClassName}
onSelect={({ search, item }) => { onSelect={({ search, item }) => {
if (item) { if (item) {
arg.opt_set_value({ arg.opt_set_value?.({
fm, fm,
name: field.name, name: field.name,
type: field.type, type: field.type,
@ -226,7 +226,7 @@ export const TypeDropdown: FC<{
note="dropdown" note="dropdown"
popupClassName={popupClassName} popupClassName={popupClassName}
onChange={(values) => { onChange={(values) => {
arg.opt_set_value({ arg.opt_set_value?.({
fm, fm,
name: field.name, name: field.name,
type: field.type, type: field.type,

View File

@ -104,6 +104,8 @@ export const FieldTypeInput: FC<{
input.render(); input.render();
if (prop.onChange) { if (prop.onChange) {
prop.onChange(fm.data[field.name]); prop.onChange(fm.data[field.name]);
} else {
arg.on_change?.({ value: fm.data[field.name], name: field.name, fm });
} }
clearTimeout(input.change_timeout); clearTimeout(input.change_timeout);
input.change_timeout = setTimeout(fm.render, 300); input.change_timeout = setTimeout(fm.render, 300);
@ -232,6 +234,8 @@ export const FieldTypeInput: FC<{
mask="____-____-_______" mask="____-____-_______"
replacement={{ _: /\d/ }} replacement={{ _: /\d/ }}
onChange={(ev) => { onChange={(ev) => {
console.log("onchange");
fm.data[field.name] = ev.currentTarget.value.replace(/\D/g, ""); fm.data[field.name] = ev.currentTarget.value.replace(/\D/g, "");
renderOnChange(); renderOnChange();
}} }}

View File

@ -1,16 +1,17 @@
import { useLocal } from "lib/utils/use-local"; import ExcelJS from "exceljs";
import { FC, MouseEvent } from "react"; import { FC, MouseEvent } from "react";
// import ExcelJS from "exceljs";
export const ExportExcel: FC<{ export const ExportExcel: FC<{
data: any[]; data: any[];
fileName?: string; fileName?: string;
}> = ({ data, fileName = "exported_data.xlsx" }): JSX.Element => { children?: any;
const local = useLocal({ className?: string;
data: [] as any[], }> = ({
}); data,
local.data = data; fileName = "exported_data.xlsx",
local.render(); children,
className,
}): JSX.Element => {
const getAllKeys = (arr: Array<Record<string, any>>): string[] => { const getAllKeys = (arr: Array<Record<string, any>>): string[] => {
const keysSet = new Set<string>(); const keysSet = new Set<string>();
@ -20,57 +21,30 @@ export const ExportExcel: FC<{
return Array.from(keysSet); return Array.from(keysSet);
}; };
// const handleExport = async (e: MouseEvent<HTMLButtonElement>) => {
// try {
// const workbook = new ExcelJS.Workbook();
// const worksheet = workbook.addWorksheet("Sheet 1");
// const columns = getAllKeys(local.data);
// worksheet.addRow(columns);
// local.data.forEach((row) => {
// const values = columns.map((col) => row[col]);
// worksheet.addRow(values);
// });
// const buffer = await workbook.xlsx.writeBuffer();
// const blob = new Blob([buffer], {
// type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
// });
// // FileSaver.saveAs(blob, fileName);
// console.log("Data exported");
// } catch (error) {
// console.error("Error exporting data:", error);
// }
// };
const handleExport = async (e: MouseEvent<HTMLButtonElement>) => { const handleExport = async (e: MouseEvent<HTMLButtonElement>) => {
try { try {
// const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook();
// const worksheet = workbook.addWorksheet("Sheet 1"); const worksheet = workbook.addWorksheet("Sheet 1");
// const columns = getAllKeys(local.data); const columns = getAllKeys(data);
// worksheet.addRow(columns); worksheet.addRow(columns);
// local.data.forEach((row) => { data.forEach((row) => {
// const values = columns.map((col) => row[col]); const values = columns.map((col) => row[col]);
// worksheet.addRow(values); worksheet.addRow(values);
// }); });
// const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
// const blob = new Blob([buffer], { const blob = new Blob([buffer], {
// type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
// }); });
// const a = document.createElement("a"); const a = document.createElement("a");
// a.href = window.URL.createObjectURL(blob); a.href = window.URL.createObjectURL(blob);
// a.download = "my-exported-data.xlsx"; a.download = fileName;
// a.click(); a.click();
console.log("Data exported"); console.log("Data exported");
} catch (error) { } catch (error) {
@ -79,10 +53,8 @@ export const ExportExcel: FC<{
}; };
return ( return (
<div> <button onClick={handleExport} className={className}>
<button onClick={handleExport} style={{ background: "#00ffff" }}> {children || "Export"}
Export </button>
</button>
</div>
); );
}; };

View File

@ -1,8 +1,4 @@
import { GFCol, parseGenField } from "lib/gen/utils"; import { parseGenField } from "lib/gen/utils";
import { cn } from "lib/utils";
import { fields_map } from "lib/utils/format-value";
import { call_prasi_events } from "lib/utils/prasi-events";
import { set } from "lib/utils/set";
import { useLocal } from "lib/utils/use-local"; import { useLocal } from "lib/utils/use-local";
import get from "lodash.get"; import get from "lodash.get";
import { import {
@ -12,77 +8,20 @@ import {
Loader2, Loader2,
Sticker, Sticker,
} from "lucide-react"; } from "lucide-react";
import { import { ChangeEvent, FC, MouseEvent, useEffect } from "react";
ChangeEvent, import DataGrid, { ColumnOrColumnGroup, Row } from "react-data-grid";
FC,
MouseEvent,
ReactElement,
ReactNode,
useEffect,
} from "react";
import DataGrid, {
ColumnOrColumnGroup,
RenderCellProps,
Row,
SortColumn,
} from "react-data-grid";
import "react-data-grid/lib/styles.css"; import "react-data-grid/lib/styles.css";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { filterWhere } from "../filter/parser/filter-where";
import { getFilter } from "../filter/utils/get-filter"; import { getFilter } from "../filter/utils/get-filter";
import { MDLocal } from "../md/utils/typings";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
import { toast, Toaster } from "../ui/toast"; import { toast, Toaster } from "../ui/toast";
import { TableListProp, useTableListLocal } from "./TableListLocal";
import { TLList } from "./TLList"; import { TLList } from "./TLList";
import { TLSlider } from "./TLSlider"; import { TLSlider } from "./TLSlider";
import { sortTree } from "./utils/sort-tree"; import { sortTree } from "./utils/sort-tree";
import { OnRowClick } from "./utils/type";
let EMPTY_SET = new Set() as ReadonlySet<any>; let EMPTY_SET = new Set() as ReadonlySet<any>;
type SelectedRow = (arg: {
row: any;
rows: any[];
idx: any;
select?: boolean;
data?: any[];
}) => boolean;
type TableListProp = {
child: any;
PassProp: any;
list: { type: string; item_w: string };
name: string;
value?: any[];
on_load?: (arg: {
reload: () => Promise<void>;
orderBy?: Record<string, "asc" | "desc" | Record<string, "asc" | "desc">>;
paging: { take: number; skip: number };
mode: "count" | "query";
}) => Promise<any[]>;
on_init: (arg?: any) => any;
mode: "table" | "list" | "grid" | "auto";
_item: PrasiItem;
__props?: any;
gen_fields: string[];
row_click: OnRowClick;
selected: SelectedRow;
show_header?: boolean;
id_parent?: string;
feature?: Array<any>;
filter_name: string;
render_row?: (child: any, data: any) => ReactNode;
row_height?: number | ((row: any) => number);
render_col?: (arg: {
props: RenderCellProps<any, unknown>;
tbl: any;
child: any;
}) => ReactNode;
gen_table?: string;
softdel_type?: string;
paging?: boolean;
cache_row?: boolean;
md?: MDLocal;
};
const w = window as any; const w = window as any;
const selectCellClassname = css` const selectCellClassname = css`
display: flex; display: flex;
@ -94,30 +33,31 @@ const selectCellClassname = css`
} }
`; `;
export const TableList: FC<TableListProp> = ({ export const TableList: FC<TableListProp> = (props) => {
name, const {
on_load, name,
child, on_load,
PassProp, child,
mode: _mode, PassProp,
on_init, mode: _mode,
_item, on_init,
gen_fields, _item,
row_click, gen_fields,
selected, row_click,
id_parent, selected,
feature, id_parent,
filter_name, feature,
row_height: rowHeight, filter_name,
render_col, row_height: rowHeight,
show_header, render_col,
list, show_header,
value, list,
paging, value,
cache_row, paging,
__props, cache_row,
md, __props,
}) => { md,
} = props;
let mode = _mode; let mode = _mode;
if (mode === "auto") { if (mode === "auto") {
if (w.isMobile) { if (w.isMobile) {
@ -127,286 +67,7 @@ export const TableList: FC<TableListProp> = ({
} }
} }
let ls_sort = localStorage.getItem( const local = useTableListLocal(props);
`sort-${location.pathname}-${location.hash}-${name}`
) as unknown as { columns: any; orderBy: any };
if (ls_sort) {
ls_sort = JSON.parse(ls_sort as any);
}
const local = useLocal(
{
times: 0,
selectedRows: [] as {
pk: string | number;
rows: any;
}[],
el: null as null | HTMLDivElement,
width: 0,
height: 0,
selectedAllRows: false as boolean,
selectedRowIds: [] as (string | number)[],
pk: null as null | GFCol,
scrolled: false,
data: [] as any[],
status: "init" as
| "loading"
| "ready"
| "resizing"
| "reload"
| "init"
| "error",
where: null as any,
firstKey: "",
should_toast: true,
paging: {
take: 100,
skip: 0,
timeout: null as any,
total: 0,
last_length: 0,
scroll: (currentTarget: HTMLDivElement) => {
if (
isEditor ||
local.data.length < local.paging.take ||
local.data.length === 0 ||
local.status !== "ready" ||
!isAtBottom(currentTarget) ||
local.reloading
)
return;
if (local.paging.last_length <= local.data.length) {
local.paging.skip = local.data.length;
local.reload();
}
},
},
grid_ref: null as null | HTMLDivElement,
collapsed: new Set<number>(),
cached_row: new WeakMap<any, ReactElement>(),
filtering: "" as ReactNode | string | true,
reloading: null as any,
reload: (arg?: { toast: boolean }) => {
if (local.reloading) return local.reloading;
local.reloading = new Promise<void>(async (done) => {
let should_toast = true;
if (arg?.toast === false) should_toast = false;
local.should_toast = should_toast;
local.filtering = "";
if (typeof on_load === "function") {
local.status = "loading";
local.render();
const orderBy = local.sort.orderBy || undefined;
const where = filterWhere(filter_name, __props);
if (where?.OR?.length > 0) {
const key = Object.keys(where.OR[0])[0];
if (key && where.OR[0][key]) {
let filtering = where.OR[0][key].contains;
if (typeof local.filtering === "string" && filtering) {
filtering = filtering.slice(1, -1);
} else {
filtering = "";
}
if (filtering) {
local.filtering = (
<div className="c-pt-2">
Searching for: <pre>"{filtering.trim()}"</pre>
</div>
);
}
}
}
if (md) {
await new Promise<void>((resolve) => {
const ival = setInterval(() => {
if (!md.header.loading) {
clearInterval(ival);
resolve();
}
}, 10);
});
if (
Array.isArray(md?.params?.links) &&
md?.params?.links?.length
) {
const last = md.params.links[md.params.links.length - 1];
if (last && last.where) {
if ((last.name && last.name === md.name) || !last.name) {
for (const [k, v] of Object.entries(last.where)) {
where[k] = v;
}
}
}
}
}
call_prasi_events("tablelist", "where", [
__props?.gen__table,
where,
]);
const load_args: any = {
async reload() {},
orderBy,
where,
paging: {
take: local.paging.take > 0 ? local.paging.take : undefined,
skip: local.paging.skip,
},
};
if (id_parent) {
load_args.paging = {};
}
const result = on_load({ ...load_args, mode: "query" });
const callback = (data: any[]) => {
if (
id_parent ||
!local.paging ||
(local.paging && !local.paging.take) ||
local.paging.skip === 0
) {
local.data = data;
} else {
local.data = [...local.data, ...data];
}
local.paging.last_length = local.data.length;
local.status = "ready";
local.reloading = null;
local.render();
done();
setTimeout(() => {
if (
local.grid_ref &&
!id_parent &&
(paging !== undefined || paging)
) {
local.paging.scroll(local.grid_ref);
}
}, 100);
};
if (result instanceof Promise) {
(async () => {
try {
callback(await result);
} catch (e) {
console.error(e);
local.status = "error";
toast.dismiss();
toast.error(
<div className="c-flex c-text-red-600 c-items-center">
<AlertTriangle className="c-h-4 c-w-4 c-mr-1" />
Failed to load data
</div>,
{
dismissible: true,
className: css`
background: #ffecec;
border: 2px solid red;
`,
}
);
}
})();
} else callback(result);
}
});
return local.reloading;
},
sort: {
columns: (ls_sort?.columns || []) as SortColumn[],
on_change: (cols: SortColumn[]) => {
if (feature?.find((e) => e === "sorting")) {
local.sort.columns = cols;
local.paging.skip = 0;
if (cols.length > 0) {
let { columnKey, direction } = cols[0];
if (columnKey.includes(".")) {
let root: any = {};
set(root, columnKey, direction === "ASC" ? "asc" : "desc");
local.sort.orderBy = root;
} else {
let should_set = true;
const gf = JSON.stringify(gen_fields);
const fields = fields_map.get(gf);
if (fields) {
const rel = fields?.find((e) => e.name === columnKey);
if (rel && rel.checked) {
should_set = false;
if (rel.type === "has-many") {
local.sort.orderBy = {
[columnKey]: {
_count: direction === "ASC" ? "asc" : "desc",
},
};
} else {
const field = rel.checked.find((e) => !e.is_pk);
if (field) {
local.sort.orderBy = {
[columnKey]: {
[field.name]: direction === "ASC" ? "asc" : "desc",
},
};
} else if (rel.relation) {
local.sort.orderBy = {
[columnKey]: {
[rel.relation.to.fields[0]]:
direction === "ASC" ? "asc" : "desc",
},
};
}
}
}
}
if (should_set) {
local.sort.orderBy = {
[columnKey]: direction === "ASC" ? "asc" : "desc",
};
}
}
} else {
local.sort.orderBy = null;
}
localStorage.setItem(
`sort-${location.pathname}-${location.hash}-${name}`,
JSON.stringify({
columns: local.sort.columns,
orderBy: local.sort.orderBy,
})
);
local.status = "reload";
local.render();
}
},
orderBy: (ls_sort?.orderBy || null) as null | Record<
string,
"asc" | "desc" | Record<string, "asc" | "desc">
>,
},
soft_delete: {
field: null as any,
},
},
({ setDelayedRender }) => {
setDelayedRender(true);
}
);
const reload = local.reload; const reload = local.reload;
if (md) { if (md) {
@ -788,11 +449,9 @@ export const TableList: FC<TableListProp> = ({
}} }}
> >
<div <div
className={cx( className={cx(css`
css` width: 16px;
width: 16px; `)}
`
)}
> >
{props.row?.__children?.length > 0 && ( {props.row?.__children?.length > 0 && (
<> <>
@ -1173,13 +832,6 @@ const dataGridStyle = (local: { el: null | HTMLDivElement }) => {
`; `;
}; };
function isAtBottom(currentTarget: HTMLDivElement): boolean {
return (
currentTarget.scrollTop + 10 >=
currentTarget.scrollHeight - currentTarget.clientHeight
);
}
function getProp(child: any, name: string, defaultValue?: any) { function getProp(child: any, name: string, defaultValue?: any) {
const fn = new Function( const fn = new Function(
`return ${get(child, `component.props.${name}.valueBuilt`) || `null`}` `return ${get(child, `component.props.${name}.valueBuilt`) || `null`}`

374
comps/list/TableListLocal.tsx Executable file
View File

@ -0,0 +1,374 @@
import { GFCol } from "lib/gen/utils";
import { fields_map } from "lib/utils/format-value";
import { call_prasi_events } from "lib/utils/prasi-events";
import { set } from "lib/utils/set";
import { useLocal } from "lib/utils/use-local";
import { AlertTriangle } from "lucide-react";
import { ReactElement, ReactNode } from "react";
import { RenderCellProps, SortColumn } from "react-data-grid";
import "react-data-grid/lib/styles.css";
import { filterWhere } from "../filter/parser/filter-where";
import { MDLocal } from "../md/utils/typings";
import { toast } from "../ui/toast";
import { OnRowClick } from "./utils/type";
type SelectedRow = (arg: {
row: any;
rows: any[];
idx: any;
select?: boolean;
data?: any[];
}) => boolean;
export type TableListProp = {
child: any;
PassProp: any;
list: { type: string; item_w: string };
name: string;
value?: any[];
on_load?: (arg: {
reload: () => Promise<void>;
orderBy?: Record<string, "asc" | "desc" | Record<string, "asc" | "desc">>;
paging: { take: number; skip: number };
mode: "count" | "query";
}) => Promise<any[]>;
on_init: (arg?: any) => any;
mode: "table" | "list" | "grid" | "auto";
_item: PrasiItem;
__props?: any;
gen_fields: string[];
row_click: OnRowClick;
selected: SelectedRow;
show_header?: boolean;
id_parent?: string;
feature?: Array<any>;
filter_name: string;
render_row?: (child: any, data: any) => ReactNode;
row_height?: number | ((row: any) => number);
render_col?: (arg: {
props: RenderCellProps<any, unknown>;
tbl: any;
child: any;
}) => ReactNode;
gen_table?: string;
softdel_type?: string;
paging?: boolean;
cache_row?: boolean;
md?: MDLocal;
};
export type TableListLocal = ReturnType<typeof useTableListLocal>;
export const useTableListLocal = ({
name,
on_load,
child,
PassProp,
mode: _mode,
on_init,
_item,
gen_fields,
row_click,
selected,
id_parent,
feature,
filter_name,
row_height: rowHeight,
render_col,
show_header,
list,
value,
paging,
cache_row,
__props,
md,
}: TableListProp) => {
let ls_sort = localStorage.getItem(
`sort-${location.pathname}-${location.hash}-${name}`
) as unknown as { columns: any; orderBy: any };
if (ls_sort) {
ls_sort = JSON.parse(ls_sort as any);
}
const local = useLocal(
{
times: 0,
selectedRows: [] as {
pk: string | number;
rows: any;
}[],
el: null as null | HTMLDivElement,
width: 0,
height: 0,
selectedAllRows: false as boolean,
selectedRowIds: [] as (string | number)[],
pk: null as null | GFCol,
scrolled: false,
data: [] as any[],
status: "init" as
| "loading"
| "ready"
| "resizing"
| "reload"
| "init"
| "error",
where: null as any,
firstKey: "",
should_toast: true,
paging: {
take: 100,
skip: 0,
timeout: null as any,
total: 0,
last_length: 0,
scroll: (currentTarget: HTMLDivElement) => {
if (
isEditor ||
local.data.length < local.paging.take ||
local.data.length === 0 ||
local.status !== "ready" ||
!isAtBottom(currentTarget) ||
local.reloading
)
return;
if (local.paging.last_length <= local.data.length) {
local.paging.skip = local.data.length;
local.reload();
}
},
},
grid_ref: null as null | HTMLDivElement,
collapsed: new Set<number>(),
cached_row: new WeakMap<any, ReactElement>(),
filtering: "" as ReactNode | string | true,
reloading: null as any,
reload: (arg?: { toast: boolean }) => {
if (local.reloading) return local.reloading;
local.reloading = new Promise<void>(async (done) => {
let should_toast = true;
if (arg?.toast === false) should_toast = false;
local.should_toast = should_toast;
local.filtering = "";
if (typeof on_load === "function") {
local.status = "loading";
local.render();
const orderBy = local.sort.orderBy || undefined;
const where = filterWhere(filter_name, __props);
if (where?.OR?.length > 0) {
const key = Object.keys(where.OR[0])[0];
if (key && where.OR[0][key]) {
let filtering = where.OR[0][key].contains;
if (typeof local.filtering === "string" && filtering) {
filtering = filtering.slice(1, -1);
} else {
filtering = "";
}
if (filtering) {
local.filtering = (
<div className="c-pt-2">
Searching for: <pre>"{filtering.trim()}"</pre>
</div>
);
}
}
}
if (md) {
await new Promise<void>((resolve) => {
const ival = setInterval(() => {
if (!md.header.loading) {
clearInterval(ival);
resolve();
}
}, 10);
});
if (
Array.isArray(md?.params?.links) &&
md?.params?.links?.length
) {
const last = md.params.links[md.params.links.length - 1];
if (last && last.where) {
if ((last.name && last.name === md.name) || !last.name) {
for (const [k, v] of Object.entries(last.where)) {
where[k] = v;
}
}
}
}
}
call_prasi_events("tablelist", "where", [
__props?.gen__table,
where,
]);
const load_args: any = {
async reload() {},
orderBy,
where,
paging: {
take: local.paging.take > 0 ? local.paging.take : undefined,
skip: local.paging.skip,
},
};
if (id_parent) {
load_args.paging = {};
}
const result = on_load({ ...load_args, mode: "query" });
const callback = (data: any[]) => {
if (
id_parent ||
!local.paging ||
(local.paging && !local.paging.take) ||
local.paging.skip === 0
) {
local.data = data;
} else {
local.data = [...local.data, ...data];
}
local.paging.last_length = local.data.length;
local.status = "ready";
local.reloading = null;
local.render();
done();
setTimeout(() => {
if (
local.grid_ref &&
!id_parent &&
(paging !== undefined || paging)
) {
local.paging.scroll(local.grid_ref);
}
}, 100);
};
if (result instanceof Promise) {
(async () => {
try {
callback(await result);
} catch (e) {
console.error(e);
local.status = "error";
toast.dismiss();
toast.error(
<div className="c-flex c-text-red-600 c-items-center">
<AlertTriangle className="c-h-4 c-w-4 c-mr-1" />
Failed to load data
</div>,
{
dismissible: true,
className: css`
background: #ffecec;
border: 2px solid red;
`,
}
);
}
})();
} else callback(result);
}
});
return local.reloading;
},
sort: {
columns: (ls_sort?.columns || []) as SortColumn[],
on_change: (cols: SortColumn[]) => {
if (feature?.find((e) => e === "sorting")) {
local.sort.columns = cols;
local.paging.skip = 0;
if (cols.length > 0) {
let { columnKey, direction } = cols[0];
if (columnKey.includes(".")) {
let root: any = {};
set(root, columnKey, direction === "ASC" ? "asc" : "desc");
local.sort.orderBy = root;
} else {
let should_set = true;
const gf = JSON.stringify(gen_fields);
const fields = fields_map.get(gf);
if (fields) {
const rel = fields?.find((e) => e.name === columnKey);
if (rel && rel.checked) {
should_set = false;
if (rel.type === "has-many") {
local.sort.orderBy = {
[columnKey]: {
_count: direction === "ASC" ? "asc" : "desc",
},
};
} else {
const field = rel.checked.find((e) => !e.is_pk);
if (field) {
local.sort.orderBy = {
[columnKey]: {
[field.name]: direction === "ASC" ? "asc" : "desc",
},
};
} else if (rel.relation) {
local.sort.orderBy = {
[columnKey]: {
[rel.relation.to.fields[0]]:
direction === "ASC" ? "asc" : "desc",
},
};
}
}
}
}
if (should_set) {
local.sort.orderBy = {
[columnKey]: direction === "ASC" ? "asc" : "desc",
};
}
}
} else {
local.sort.orderBy = null;
}
localStorage.setItem(
`sort-${location.pathname}-${location.hash}-${name}`,
JSON.stringify({
columns: local.sort.columns,
orderBy: local.sort.orderBy,
})
);
local.status = "reload";
local.render();
}
},
orderBy: (ls_sort?.orderBy || null) as null | Record<
string,
"asc" | "desc" | Record<string, "asc" | "desc">
>,
},
soft_delete: {
field: null as any,
},
},
({ setDelayedRender }) => {
setDelayedRender(true);
}
);
return local;
};
function isAtBottom(currentTarget: HTMLDivElement): boolean {
return (
currentTarget.scrollTop + 10 >=
currentTarget.scrollHeight - currentTarget.clientHeight
);
}

View File

@ -33,10 +33,6 @@ export const MDRenderMaster: FC<{
size: width, size: width,
min_size: min_width, min_size: min_width,
}); });
// if (md.panel) {
// md.panel.min_size = min_width;
// md.panel.size = width;
// }
} }
}, Object.values(md.deps || {}) || []); }, Object.values(md.deps || {}) || []);

View File

@ -1,7 +1,8 @@
import { BreadItem } from "lib/comps/custom/Breadcrumb"; import { BreadItem } from "lib/comps/custom/Breadcrumb";
import { FMLocal } from "lib/comps/form/typings";
import { GFCol } from "lib/gen/utils";
import { LinkParam } from "lib/comps/form/field/type/TypeLink"; import { LinkParam } from "lib/comps/form/field/type/TypeLink";
import { FMLocal } from "lib/comps/form/typings";
import { TableListLocal } from "lib/comps/list/TableListLocal";
import { GFCol } from "lib/gen/utils";
import { ReactNode } from "react"; import { ReactNode } from "react";
type ID_MASTER_DETAIL = string; type ID_MASTER_DETAIL = string;
@ -58,7 +59,7 @@ export type MDLocalInternal = {
master: { master: {
reload: (arg?: { toast: boolean }) => void; reload: (arg?: { toast: boolean }) => void;
render: () => void; render: () => void;
list?: any; list?: TableListLocal;
pk?: string; pk?: string;
}; };
params: { params: {
@ -68,6 +69,7 @@ export type MDLocalInternal = {
parse: () => void; parse: () => void;
apply: () => void; apply: () => void;
}; };
pk?: GFCol; pk?: GFCol;
props: { props: {
mode: "full" | "h-split" | "v-split"; mode: "full" | "h-split" | "v-split";

View File

@ -42,7 +42,7 @@ export const Typeahead: FC<{
onChange, onChange,
className, className,
popupClassName, popupClassName,
disabledSearch disabledSearch,
}) => { }) => {
const local = useLocal({ const local = useLocal({
value: [] as string[], value: [] as string[],
@ -309,21 +309,23 @@ export const Typeahead: FC<{
} }
} }
const valueLabel = local.value?.map((value) => { const valueLabel = local.value
if (local.mode === "single") { ?.map((value) => {
const item = options.find((item) => item.value === value); if (local.mode === "single") {
const item = options.find((item) => item.value === value);
if (!local.open && !allow_new) { if (!local.open && !allow_new) {
local.select = item || null; local.select = item || null;
local.search.input = item?.tag || item?.label || ""; local.search.input = item?.tag || item?.label || "";
}
return item;
} }
return item;
}
const item = local.options.find((e) => e.value === value); const item = local.options.find((e) => e.value === value);
return item; return item;
}); })
.filter((e) => e);
let inputval = local.search.input; let inputval = local.search.input;

View File

@ -8,7 +8,9 @@
"@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"input-otp": "^1.4.1",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"input-otp": "^1.4.1",
"@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-progress": "^1.1.0",
@ -23,10 +25,8 @@
"@types/autosize": "^4.0.3", "@types/autosize": "^4.0.3",
"@types/lodash.capitalize": "^4.2.9", "@types/lodash.capitalize": "^4.2.9",
"@types/lodash.get": "^4.4.9", "@types/lodash.get": "^4.4.9",
"@types/react": "^18.2.65",
"drizzle-orm": "^0.33.0", "drizzle-orm": "^0.33.0",
"bun-types": "^1.1.24", "bun-types": "^1.1.24",
"@types/react-dom": "^18.3.0",
"rou3": "^0.5.1", "rou3": "^0.5.1",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@wojtekmaj/react-qr-svg": "^1.0.0", "@wojtekmaj/react-qr-svg": "^1.0.0",

View File

@ -1,8 +1,8 @@
import { FieldLoading } from "lib/comps/ui/field-loading";
import { getBasename } from "lib/utils/pathname";
import { useLocal } from "lib/utils/use-local"; import { useLocal } from "lib/utils/use-local";
import { FC } from "react"; import { FC } from "react";
import { FieldLoading } from "../../..";
import { loadSession } from "./utils/load"; import { loadSession } from "./utils/load";
import { getBasename } from "lib/utils/pathname";
const w = window as unknown as { const w = window as unknown as {
user: any; user: any;

View File

@ -70,7 +70,15 @@ export const useServerRouter = <T extends ReturnType<typeof newServerRouter>>(
} }
return { return {
async handle(arg: ServerContext | SessionContext<any>) { async handle(
arg: ServerContext | SessionContext<any>,
opt?: {
rewrite?: (arg: {
body: Bun.BodyInit;
headers?: Record<string, string>;
}) => Bun.BodyInit;
}
) {
const { url, req, handle } = arg; const { url, req, handle } = arg;
const found = findRoute(rou, undefined, url.pathname); const found = findRoute(rou, undefined, url.pathname);
if (found) { if (found) {
@ -101,7 +109,7 @@ export const useServerRouter = <T extends ReturnType<typeof newServerRouter>>(
return new Response(JSON.stringify(result)); return new Response(JSON.stringify(result));
} }
return handle(req); return handle(req, opt);
}, },
}; };
}; };

View File

@ -1,14 +1,20 @@
/// <reference types="bun-types" />
import { ServerWebSocket } from "bun"; import { ServerWebSocket } from "bun";
import { useServerRouter } from "../server/server-route"; import { useServerRouter } from "../server/server-route";
import { newSessionStore } from "./store/session-store"; import { newSessionStore } from "./store/session-store";
import { ServerContext } from "./type"; import { ServerContext } from "./type";
type WS = ServerWebSocket<{ url: string }>; type WS = ServerWebSocket<{ url: string }>;
type SessionServerHandler = { export type SessionServerHandler = {
cleanup: () => Promise<void>; cleanup: () => Promise<void>;
handle: (arg: ServerContext) => Promise<Response>; handle: (
arg: ServerContext,
opt?: {
rewrite?: (arg: {
body: Bun.BodyInit;
headers?: Record<string, string>;
}) => Bun.BodyInit;
}
) => Promise<Response>;
}; };
export const initSessionServer = <T>( export const initSessionServer = <T>(
@ -24,7 +30,7 @@ export const initSessionServer = <T>(
const server_handler: SessionServerHandler = { const server_handler: SessionServerHandler = {
async cleanup() {}, async cleanup() {},
async handle(server_arg) { async handle(server_arg, opt) {
const { req, handle, url } = server_arg; const { req, handle, url } = server_arg;
const route_arg = { const route_arg = {
@ -36,16 +42,16 @@ export const initSessionServer = <T>(
}; };
if (url.pathname.startsWith("/_session/")) { if (url.pathname.startsWith("/_session/")) {
const res = await session_router.handle(route_arg); const res = await session_router.handle(route_arg, opt);
if (res) return res; if (res) return res;
} }
if (arg.router) { if (arg.router) {
const res = await arg.router.handle(route_arg); const res = await arg.router.handle(route_arg, opt);
if (res) return res; if (res) return res;
} }
return handle(req); return handle(req, opt);
}, },
}; };

View File

@ -71,7 +71,15 @@ export interface SessionContext<T> extends ServerContext {
export type ServerContext = { export type ServerContext = {
req: Request; req: Request;
server: Server; server: Server;
handle: (req: Request) => Promise<Response>; handle: (
req: Request,
opt?: {
rewrite?: (arg: {
body: Bun.BodyInit;
headers?: Record<string, string>;
}) => Bun.BodyInit;
}
) => Promise<Response>;
mode: "dev" | "prod"; mode: "dev" | "prod";
url: { url: {
raw: URL; raw: URL;
@ -79,8 +87,16 @@ export type ServerContext = {
}; };
}; };
export type SessionAuth = { export type SessionAuth = (
method: "user-pass"; | {
username: string; method: "user-pass";
password: string; username: string;
} & Record<string, any>; password: string;
}
| {
method: "user-otp";
uid: string;
otp: string;
}
) &
Record<string, any>;

View File

@ -1,6 +0,0 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -4,6 +4,7 @@ export const baseurl = (url: string) => {
location.host === "localhost:4550" location.host === "localhost:4550"
) { ) {
const id_site = location.pathname.split("/")[2]; const id_site = location.pathname.split("/")[2];
if (url.startsWith(`/prod/${id_site}`)) return url;
if (url.startsWith("/")) return `/prod/${id_site}${url}`; if (url.startsWith("/")) return `/prod/${id_site}${url}`;
else return `/prod/${id_site}/${url}`; else return `/prod/${id_site}/${url}`;

View File

@ -1,4 +1,4 @@
import { FC, ReactNode, Suspense, lazy } from "react"; import { FC, lazy } from "react";
export const lazify = <T extends FC<any>>(fn: () => Promise<T>): T => { export const lazify = <T extends FC<any>>(fn: () => Promise<T>): T => {
return lazy(async () => { return lazy(async () => {

View File

@ -1,6 +1,5 @@
import { FieldLocal } from "lib/comps/form/typings"; import { FieldLocal, FMLocal } from "lib/comps/form/typings";
import { MDLocal } from "lib/comps/md/utils/typings"; import { MDLocal } from "lib/comps/md/utils/typings";
import { FMLocal } from "../..";
//@ts-ignore //@ts-ignore
import { Prisma } from "../../typings/prisma"; import { Prisma } from "../../typings/prisma";
import { set } from "./set"; import { set } from "./set";