fix category

This commit is contained in:
rizky 2024-08-12 04:34:44 -07:00
parent 362cfdff27
commit ad86c97c9a
9 changed files with 227 additions and 164 deletions

View File

@ -13,8 +13,8 @@ export const FilterField: FC<{
name?: string; name?: string;
label?: string; label?: string;
type: FilterFieldType; type: FilterFieldType;
modifiers?: any[] modifiers?: any[];
}> = ({ filter, name, label, type,modifiers }) => { }> = ({ filter, name, label, type, modifiers }) => {
const internal = useLocal({ render_timeout: null as any }); const internal = useLocal({ render_timeout: null as any });
if (!name) return <>No Name</>; if (!name) return <>No Name</>;
if (!filter.form) return <div>Loading...</div>; if (!filter.form) return <div>Loading...</div>;
@ -85,7 +85,7 @@ export const FilterField: FC<{
prop={{ prop={{
type: "input", type: "input",
sub_type: "search", sub_type: "search",
placeholder: "Search...", placeholder: field.field.label,
onBlur(e) { onBlur(e) {
filter.form?.submit(); filter.form?.submit();
}, },

View File

@ -71,6 +71,8 @@ export const Field: FC<FieldProp> = (arg) => {
{...props} {...props}
className={cx( className={cx(
"field", "field",
field.type,
sub_type,
"c-flex c-relative", "c-flex c-relative",
editorClassName, editorClassName,
field.type === "single-option" && sub_type === "checkbox" field.type === "single-option" && sub_type === "checkbox"

View File

@ -290,3 +290,47 @@ const getFileName = (url: string) => {
const extension = fileName.substring(dotIndex + 1); const extension = fileName.substring(dotIndex + 1);
return { name, extension, fullname }; 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>
);
};

View File

@ -262,7 +262,7 @@ export const FieldUploadMulti: FC<{
<div className="c-flex c-pt-1"> <div className="c-flex c-pt-1">
<div <div
className={cx( 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` css`
&:hover { &:hover {
border: 1px solid #1c4ed8; border: 1px solid #1c4ed8;

View File

@ -35,9 +35,9 @@ 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 { MDLocal } from "../md/utils/typings";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
import { sortTree } from "./utils/sort-tree";
import { toast } from "../ui/toast"; 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: { type OnRowClick = (arg: {
row: any; row: any;
@ -176,6 +176,104 @@ export const TableList: FC<TableListProp> = ({
collapsed: new Set<number>(), collapsed: new Set<number>(),
cached_row: new WeakMap<any, ReactElement>(), cached_row: new WeakMap<any, ReactElement>(),
filtering: "" as ReactNode | string | true, 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: { sort: {
columns: (ls_sort?.columns || []) as SortColumn[], columns: (ls_sort?.columns || []) as SortColumn[],
on_change: (cols: SortColumn[]) => { on_change: (cols: SortColumn[]) => {
@ -255,104 +353,7 @@ export const TableList: FC<TableListProp> = ({
}, },
}); });
const reload = useCallback( const reload = local.reload;
(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]
);
if (md) { if (md) {
md.master.reload = reload; 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 // code ini digunakan untuk mengambil nama dari pk yang akan digunakan sebagai key untuk id
const pk = local.pk?.name || "id"; const pk = local.pk?.name || "id";
useEffect(() => { useEffect(() => {
if (isEditor || value) { if (isEditor || value) {
on_init(local); on_init(local);
@ -373,7 +375,10 @@ export const TableList: FC<TableListProp> = ({
} }
(async () => { (async () => {
on_init(local); on_init(local);
if (local.status === "reload" && typeof on_load === "function") { if (
(local.status === "init" || local.status === "reload") &&
typeof on_load === "function"
) {
reload(); reload();
} }
})(); })();
@ -703,33 +708,22 @@ export const TableList: FC<TableListProp> = ({
if (columns.length > 1) columns = columns.slice(0, 0 + 1); 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.status === "loading") {
if (local.should_toast) {
toast.dismiss(); toast.dismiss();
toast.loading( toast.loading(
<> <>
<Loader2 className="c-h-4 c-w-4 c-animate-spin" /> <Loader2 className="c-h-4 c-w-4 c-animate-spin" />
Loading {local.paging.skip === 0 ? "Data" : "more rows"} ... Loading Data ...
</>, </>
{
dismissible: true,
}
); );
} else { } else {
local.should_toast = true;
}
} else {
if (local.status !== "error") {
toast.dismiss(); toast.dismiss();
} }
}
if (local.status === "resizing" && !isEditor) {
local.status = "ready";
local.render();
return null;
} }
if (document.getElementsByClassName("prasi-toaster").length === 0) { 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)} {toaster_el && createPortal(<Toaster cn={cn} />, toaster_el)}
{local.status === "init" ? (
<DataGrid {local.status !== "ready" ? (
style={{ opacity: 0 }} <div className="c-flex c-flex-col c-space-y-2 c-m-4 c-absolute c-left-0 c-top-0">
columns={[ <Skeleton className={cx("c-w-[200px] c-h-[11px]")} />
{ <Skeleton className={cx("c-w-[170px] c-h-[11px]")} />
key: "_", <Skeleton className={cx("c-w-[180px] c-h-[11px]")} />
name: "", </div>
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)}
/>
) : ( ) : (
<div className="c-absolute c-inset-0">
<> <>
{Array.isArray(data) && data.length > 0 ? ( {Array.isArray(data) && data.length > 0 ? (
<div <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 "> <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} /> <Sticker size={35} strokeWidth={1} />
<div className="c-pt-1"> <div className="c-pt-1 c-text-center">
No&nbsp;Data No&nbsp;Data
<br /> <br />
{local.filtering && ( {local.filtering && (
@ -983,8 +961,8 @@ export const TableList: FC<TableListProp> = ({
</div> </div>
)} )}
</> </>
)}
</div> </div>
)}
</div> </div>
); );
} else { } else {

View File

@ -1,20 +1,51 @@
import { FC, useState } from "react"; import { FC, ReactNode, useEffect, useState } from "react";
import { breadcrumbPrefix } from "../utils/md-hash"; import { breadcrumbPrefix } from "../utils/md-hash";
import { MDLocal, MDRef } from "../utils/typings"; 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 }) => { 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 head = mdr.item.edit.props?.header.value;
const PassProp = mdr.PassProp; const PassProp = mdr.PassProp;
md.header.render = () => set({}); md.header.render = local.render;
const prefix = breadcrumbPrefix(md); const prefix = breadcrumbPrefix(md);
let breads: (BreadItem & { url: string })[] = [];
if (md.selected && md.header.child.breadcrumb) { 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) { } 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; if (md.internal.reset_detail) return null;
return <PassProp md={md}>{head}</PassProp>; return <PassProp md={md}>{head}</PassProp>;
}; };

View File

@ -81,7 +81,7 @@ export const masterDetailApplyParams = (md: MDLocal) => {
}; };
export const breadcrumbPrefix = (md: MDLocal) => { export const breadcrumbPrefix = (md: MDLocal) => {
const prefix: BreadItem[] = []; let prefix: (BreadItem & { url: string })[] = [];
if (md.params.links && md.params.links.length > 0) { if (md.params.links && md.params.links.length > 0) {
const hashes: string[] = []; const hashes: string[] = [];
for (const link of md.params.links) { for (const link of md.params.links) {
@ -93,6 +93,7 @@ export const breadcrumbPrefix = (md: MDLocal) => {
for (const p of link.prefix) { for (const p of link.prefix) {
prefix.push({ prefix.push({
label: p.label, label: p.label,
url: p.url || link.url,
onClick(ev) { onClick(ev) {
let url = ""; let url = "";

View File

@ -12,6 +12,7 @@ export const toast = {
sonner.dismiss(); sonner.dismiss();
} else { } else {
clearTimeout(timer.timeout); clearTimeout(timer.timeout);
timer.timeout = null;
} }
}, },
loading: ( loading: (
@ -41,6 +42,8 @@ export const toast = {
clearTimeout(timer.timeout); clearTimeout(timer.timeout);
timer.timeout = setTimeout(() => { timer.timeout = setTimeout(() => {
sonner.error(el, props); sonner.error(el, props);
clearTimeout(timer.timeout);
timer.timeout = null; timer.timeout = null;
}, timer.limit); }, timer.limit);
}, },

View File

@ -26,6 +26,10 @@ export const Typeahead = lazify(
async () => (await import("@/comps/ui/typeahead")).Typeahead async () => (await import("@/comps/ui/typeahead")).Typeahead
); );
export const ImgThumb = lazify(
async () => (await import("@/comps/form/field/type/FilePreview")).ImgThumb
);
/** Master - Detail - List - Form */ /** Master - Detail - List - Form */
export const MasterDetail = lazify( export const MasterDetail = lazify(
async () => (await import("@/comps/md/MasterDetail")).MasterDetail async () => (await import("@/comps/md/MasterDetail")).MasterDetail