fix category
This commit is contained in:
parent
362cfdff27
commit
ad86c97c9a
|
|
@ -13,7 +13,7 @@ export const FilterField: FC<{
|
|||
name?: string;
|
||||
label?: string;
|
||||
type: FilterFieldType;
|
||||
modifiers?: any[]
|
||||
modifiers?: any[];
|
||||
}> = ({ filter, name, label, type, modifiers }) => {
|
||||
const internal = useLocal({ render_timeout: null as any });
|
||||
if (!name) return <>No Name</>;
|
||||
|
|
@ -85,7 +85,7 @@ export const FilterField: FC<{
|
|||
prop={{
|
||||
type: "input",
|
||||
sub_type: "search",
|
||||
placeholder: "Search...",
|
||||
placeholder: field.field.label,
|
||||
onBlur(e) {
|
||||
filter.form?.submit();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ export const Field: FC<FieldProp> = (arg) => {
|
|||
{...props}
|
||||
className={cx(
|
||||
"field",
|
||||
field.type,
|
||||
sub_type,
|
||||
"c-flex c-relative",
|
||||
editorClassName,
|
||||
field.type === "single-option" && sub_type === "checkbox"
|
||||
|
|
|
|||
|
|
@ -290,3 +290,47 @@ const getFileName = (url: string) => {
|
|||
const extension = fileName.substring(dotIndex + 1);
|
||||
return { name, extension, fullname };
|
||||
};
|
||||
|
||||
export const ImgThumb = ({
|
||||
className,
|
||||
url,
|
||||
w,
|
||||
h,
|
||||
}: {
|
||||
className?: string;
|
||||
url: string;
|
||||
w: number;
|
||||
h: number;
|
||||
}) => {
|
||||
const local = useLocal({ error: false });
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"img-thumb",
|
||||
className,
|
||||
css`
|
||||
width: ${w}px;
|
||||
height: ${h}px;
|
||||
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%),
|
||||
linear-gradient(135deg, #ccc 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, #ccc 75%),
|
||||
linear-gradient(135deg, transparent 75%, #ccc 75%);
|
||||
background-size: 25px 25px; /* Must be a square */
|
||||
background-position: 0 0, 12.5px 0, 12.5px -12.5px, 0px 12.5px; /* Must be half of one side of the square */
|
||||
`
|
||||
)}
|
||||
>
|
||||
{!local.error && (
|
||||
<img
|
||||
onError={() => {
|
||||
local.error = true;
|
||||
local.render();
|
||||
}}
|
||||
src={siteurl(
|
||||
`/_img/${url.substring("_file/".length)}?w=${w}&h=${h}&fit=cover`
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ export const FieldUploadMulti: FC<{
|
|||
<div className="c-flex c-pt-1">
|
||||
<div
|
||||
className={cx(
|
||||
"c-flex c-border c-rounded c-cursor-pointer hover:c-bg-blue-50",
|
||||
"button c-flex c-border c-rounded c-cursor-pointer hover:c-bg-blue-50",
|
||||
css`
|
||||
&:hover {
|
||||
border: 1px solid #1c4ed8;
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ import { filterWhere } from "../filter/parser/filter-where";
|
|||
import { getFilter } from "../filter/utils/get-filter";
|
||||
import { MDLocal } from "../md/utils/typings";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import { sortTree } from "./utils/sort-tree";
|
||||
import { toast } from "../ui/toast";
|
||||
import { Arrow } from "../custom/Datepicker/components/utils";
|
||||
import { sortTree } from "./utils/sort-tree";
|
||||
import { getPathname } from "lib/exports";
|
||||
|
||||
type OnRowClick = (arg: {
|
||||
row: any;
|
||||
|
|
@ -176,6 +176,104 @@ export const TableList: FC<TableListProp> = ({
|
|||
collapsed: new Set<number>(),
|
||||
cached_row: new WeakMap<any, ReactElement>(),
|
||||
filtering: "" as ReactNode | string | true,
|
||||
reload: (arg?: { toast: boolean }) => {
|
||||
return new Promise<void>((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.slice(1, -1);
|
||||
} else {
|
||||
filtering = "";
|
||||
}
|
||||
|
||||
if (filtering) {
|
||||
local.filtering = (
|
||||
<div className="c-pt-2">
|
||||
Searching for: <pre>"{filtering.trim()}"</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (md) {
|
||||
const last = md.params.links[md.params.links.length - 1];
|
||||
|
||||
if (last && last.where) {
|
||||
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 (local.paging.skip === 0) {
|
||||
local.data = data;
|
||||
} else {
|
||||
local.data = [...local.data, ...data];
|
||||
}
|
||||
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
done();
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
sort: {
|
||||
columns: (ls_sort?.columns || []) as SortColumn[],
|
||||
on_change: (cols: SortColumn[]) => {
|
||||
|
|
@ -255,104 +353,7 @@ export const TableList: FC<TableListProp> = ({
|
|||
},
|
||||
});
|
||||
|
||||
const reload = useCallback(
|
||||
(arg?: { toast: boolean }) => {
|
||||
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.slice(1, -1);
|
||||
} else {
|
||||
filtering = "";
|
||||
}
|
||||
|
||||
if (filtering) {
|
||||
local.filtering = (
|
||||
<div className="c-pt-2">
|
||||
Searching for: <pre>"{filtering.trim()}"</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (md) {
|
||||
const last = md.params.links[md.params.links.length - 1];
|
||||
if (last && last.where) {
|
||||
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 (local.paging.skip === 0) {
|
||||
local.data = data;
|
||||
} else {
|
||||
local.data = [...local.data, ...data];
|
||||
}
|
||||
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
[on_load, local.sort.orderBy, local.paging.take, local.paging.skip]
|
||||
);
|
||||
|
||||
const reload = local.reload;
|
||||
if (md) {
|
||||
md.master.reload = reload;
|
||||
}
|
||||
|
|
@ -366,6 +367,7 @@ export const TableList: FC<TableListProp> = ({
|
|||
|
||||
// code ini digunakan untuk mengambil nama dari pk yang akan digunakan sebagai key untuk id
|
||||
const pk = local.pk?.name || "id";
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditor || value) {
|
||||
on_init(local);
|
||||
|
|
@ -373,7 +375,10 @@ export const TableList: FC<TableListProp> = ({
|
|||
}
|
||||
(async () => {
|
||||
on_init(local);
|
||||
if (local.status === "reload" && typeof on_load === "function") {
|
||||
if (
|
||||
(local.status === "init" || local.status === "reload") &&
|
||||
typeof on_load === "function"
|
||||
) {
|
||||
reload();
|
||||
}
|
||||
})();
|
||||
|
|
@ -703,33 +708,22 @@ export const TableList: FC<TableListProp> = ({
|
|||
if (columns.length > 1) columns = columns.slice(0, 0 + 1);
|
||||
}
|
||||
|
||||
if (local.status === "resizing" && !isEditor) {
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isEditor) {
|
||||
if (local.status === "loading") {
|
||||
if (local.should_toast) {
|
||||
toast.dismiss();
|
||||
toast.loading(
|
||||
<>
|
||||
<Loader2 className="c-h-4 c-w-4 c-animate-spin" />
|
||||
Loading {local.paging.skip === 0 ? "Data" : "more rows"} ...
|
||||
</>,
|
||||
{
|
||||
dismissible: true,
|
||||
}
|
||||
Loading Data ...
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
local.should_toast = true;
|
||||
}
|
||||
} else {
|
||||
if (local.status !== "error") {
|
||||
toast.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
if (local.status === "resizing" && !isEditor) {
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (document.getElementsByClassName("prasi-toaster").length === 0) {
|
||||
|
|
@ -908,32 +902,16 @@ export const TableList: FC<TableListProp> = ({
|
|||
}
|
||||
}}
|
||||
>
|
||||
<div className="c-absolute c-inset-0">
|
||||
{toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
|
||||
{local.status === "init" ? (
|
||||
<DataGrid
|
||||
style={{ opacity: 0 }}
|
||||
columns={[
|
||||
{
|
||||
key: "_",
|
||||
name: "",
|
||||
renderCell({ rowIdx }) {
|
||||
if (local.paging.take < rowIdx) {
|
||||
local.paging.take = rowIdx;
|
||||
}
|
||||
clearTimeout(local.paging.timeout);
|
||||
local.paging.timeout = setTimeout(() => {
|
||||
local.status = "reload";
|
||||
local.paging.take = local.paging.take * 5;
|
||||
local.render();
|
||||
}, 100);
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
]}
|
||||
rows={genRows(200)}
|
||||
/>
|
||||
|
||||
{local.status !== "ready" ? (
|
||||
<div className="c-flex c-flex-col c-space-y-2 c-m-4 c-absolute c-left-0 c-top-0">
|
||||
<Skeleton className={cx("c-w-[200px] c-h-[11px]")} />
|
||||
<Skeleton className={cx("c-w-[170px] c-h-[11px]")} />
|
||||
<Skeleton className={cx("c-w-[180px] c-h-[11px]")} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="c-absolute c-inset-0">
|
||||
<>
|
||||
{Array.isArray(data) && data.length > 0 ? (
|
||||
<div
|
||||
|
|
@ -965,7 +943,7 @@ export const TableList: FC<TableListProp> = ({
|
|||
) : (
|
||||
<div className="c-flex c-items-center c-justify-center c-flex-1 w-full h-full c-flex-col ">
|
||||
<Sticker size={35} strokeWidth={1} />
|
||||
<div className="c-pt-1">
|
||||
<div className="c-pt-1 c-text-center">
|
||||
No Data
|
||||
<br />
|
||||
{local.filtering && (
|
||||
|
|
@ -983,8 +961,8 @@ export const TableList: FC<TableListProp> = ({
|
|||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,51 @@
|
|||
import { FC, useState } from "react";
|
||||
import { FC, ReactNode, useEffect, useState } from "react";
|
||||
import { breadcrumbPrefix } from "../utils/md-hash";
|
||||
import { MDLocal, MDRef } from "../utils/typings";
|
||||
import { BreadItem } from "lib/comps/custom/Breadcrumb";
|
||||
import { useLocal } from "lib/utils/use-local";
|
||||
|
||||
export const MDHeader: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||
const [_, set] = useState({});
|
||||
const local = useLocal({ breads_length: 0 });
|
||||
const head = mdr.item.edit.props?.header.value;
|
||||
const PassProp = mdr.PassProp;
|
||||
md.header.render = () => set({});
|
||||
md.header.render = local.render;
|
||||
|
||||
const prefix = breadcrumbPrefix(md);
|
||||
let breads: (BreadItem & { url: string })[] = [];
|
||||
if (md.selected && md.header.child.breadcrumb) {
|
||||
md.header.breadcrumb = [...prefix, ...md.header.child.breadcrumb()];
|
||||
breads = [...prefix, ...md.header.child.breadcrumb()] as any;
|
||||
} else if (!md.selected && md.header.master.breadcrumb) {
|
||||
md.header.breadcrumb = [...prefix, ...md.header.master.breadcrumb()];
|
||||
breads = [...prefix, ...md.header.master.breadcrumb()] as any;
|
||||
}
|
||||
|
||||
md.header.breadcrumb = [];
|
||||
let overrideLabel = "" as ReactNode;
|
||||
for (const v of breads) {
|
||||
if (v.label === "--reset--") {
|
||||
md.header.breadcrumb = [];
|
||||
overrideLabel = "";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (v.url === "--override--") {
|
||||
overrideLabel = v.label;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (overrideLabel) {
|
||||
v.label = overrideLabel;
|
||||
overrideLabel = "";
|
||||
}
|
||||
md.header.breadcrumb.push(v);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (local.breads_length !== md.header.breadcrumb.length) {
|
||||
local.breads_length = md.header.breadcrumb.length;
|
||||
if (!md.selected && md.master.reload) md.master.reload();
|
||||
}
|
||||
}, [md.header.breadcrumb.length]);
|
||||
|
||||
if (md.internal.reset_detail) return null;
|
||||
return <PassProp md={md}>{head}</PassProp>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export const masterDetailApplyParams = (md: MDLocal) => {
|
|||
};
|
||||
|
||||
export const breadcrumbPrefix = (md: MDLocal) => {
|
||||
const prefix: BreadItem[] = [];
|
||||
let prefix: (BreadItem & { url: string })[] = [];
|
||||
if (md.params.links && md.params.links.length > 0) {
|
||||
const hashes: string[] = [];
|
||||
for (const link of md.params.links) {
|
||||
|
|
@ -93,6 +93,7 @@ export const breadcrumbPrefix = (md: MDLocal) => {
|
|||
for (const p of link.prefix) {
|
||||
prefix.push({
|
||||
label: p.label,
|
||||
url: p.url || link.url,
|
||||
onClick(ev) {
|
||||
let url = "";
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const toast = {
|
|||
sonner.dismiss();
|
||||
} else {
|
||||
clearTimeout(timer.timeout);
|
||||
timer.timeout = null;
|
||||
}
|
||||
},
|
||||
loading: (
|
||||
|
|
@ -41,6 +42,8 @@ export const toast = {
|
|||
clearTimeout(timer.timeout);
|
||||
timer.timeout = setTimeout(() => {
|
||||
sonner.error(el, props);
|
||||
clearTimeout(timer.timeout);
|
||||
|
||||
timer.timeout = null;
|
||||
}, timer.limit);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ export const Typeahead = lazify(
|
|||
async () => (await import("@/comps/ui/typeahead")).Typeahead
|
||||
);
|
||||
|
||||
export const ImgThumb = lazify(
|
||||
async () => (await import("@/comps/form/field/type/FilePreview")).ImgThumb
|
||||
);
|
||||
|
||||
/** Master - Detail - List - Form */
|
||||
export const MasterDetail = lazify(
|
||||
async () => (await import("@/comps/md/MasterDetail")).MasterDetail
|
||||
|
|
|
|||
Loading…
Reference in New Issue