diff --git a/comps/form/base/BaseForm.tsx b/comps/form/base/BaseForm.tsx index f666d65..5751883 100755 --- a/comps/form/base/BaseForm.tsx +++ b/comps/form/base/BaseForm.tsx @@ -43,6 +43,7 @@ export const BaseForm = >({ }, }); } + useEffect(() => { if (local.fm && local.fm.data !== data) { for (const k of Object.keys(local.fm.data)) { @@ -53,6 +54,9 @@ export const BaseForm = >({ } local.fm.render(); } + return () => { + delete (local as any).fm; + }; }, [data]); const fm = local.fm; diff --git a/comps/list/ExportExcel.tsx b/comps/list/ExportExcel.tsx index c05864a..2c0f57b 100755 --- a/comps/list/ExportExcel.tsx +++ b/comps/list/ExportExcel.tsx @@ -1,16 +1,17 @@ -import { useLocal } from "lib/utils/use-local"; +import ExcelJS from "exceljs"; import { FC, MouseEvent } from "react"; -// import ExcelJS from "exceljs"; export const ExportExcel: FC<{ data: any[]; fileName?: string; -}> = ({ data, fileName = "exported_data.xlsx" }): JSX.Element => { - const local = useLocal({ - data: [] as any[], - }); - local.data = data; - local.render(); + children?: any; + className?: string; +}> = ({ + data, + fileName = "exported_data.xlsx", + children, + className, +}): JSX.Element => { const getAllKeys = (arr: Array>): string[] => { const keysSet = new Set(); @@ -20,57 +21,31 @@ export const ExportExcel: FC<{ return Array.from(keysSet); }; - - // const handleExport = async (e: MouseEvent) => { - // 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) => { try { - // const workbook = new ExcelJS.Workbook(); - // const worksheet = workbook.addWorksheet("Sheet 1"); + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet("Sheet 1"); - // const columns = getAllKeys(local.data); - // worksheet.addRow(columns); + console.log(data); + const columns = getAllKeys(data); + worksheet.addRow(columns); - // local.data.forEach((row) => { - // const values = columns.map((col) => row[col]); - // worksheet.addRow(values); - // }); + data.forEach((row) => { + const values = columns.map((col) => row[col]); + worksheet.addRow(values); + }); - // const buffer = await workbook.xlsx.writeBuffer(); + const buffer = await workbook.xlsx.writeBuffer(); - // const blob = new Blob([buffer], { - // type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - // }); + const blob = new Blob([buffer], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); - // const a = document.createElement("a"); - // a.href = window.URL.createObjectURL(blob); - // a.download = "my-exported-data.xlsx"; - // a.click(); + const a = document.createElement("a"); + a.href = window.URL.createObjectURL(blob); + a.download = fileName; + a.click(); console.log("Data exported"); } catch (error) { @@ -79,10 +54,8 @@ export const ExportExcel: FC<{ }; return ( -
- -
+ ); }; diff --git a/comps/list/TableList.tsx b/comps/list/TableList.tsx index 9c45e5a..2a14c38 100755 --- a/comps/list/TableList.tsx +++ b/comps/list/TableList.tsx @@ -1,7 +1,4 @@ -import { GFCol, parseGenField } 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 { parseGenField } from "lib/gen/utils"; import { useLocal } from "lib/utils/use-local"; import get from "lodash.get"; import { @@ -11,77 +8,20 @@ import { Loader2, Sticker, } from "lucide-react"; -import { - ChangeEvent, - FC, - MouseEvent, - ReactElement, - ReactNode, - useEffect, -} from "react"; -import DataGrid, { - ColumnOrColumnGroup, - RenderCellProps, - Row, - SortColumn, -} from "react-data-grid"; +import { ChangeEvent, FC, MouseEvent, useEffect } from "react"; +import DataGrid, { ColumnOrColumnGroup, Row } from "react-data-grid"; import "react-data-grid/lib/styles.css"; import { createPortal } from "react-dom"; -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 { toast, Toaster } from "../ui/toast"; +import { TableListProp, useTableListLocal } from "./TableListLocal"; import { TLList } from "./TLList"; import { TLSlider } from "./TLSlider"; import { sortTree } from "./utils/sort-tree"; -import { OnRowClick } from "./utils/type"; let EMPTY_SET = new Set() as ReadonlySet; -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; - orderBy?: Record>; - paging: { take: number; skip: number }; - mode: "count" | "query"; - }) => Promise; - 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; - filter_name: string; - render_row?: (child: any, data: any) => ReactNode; - row_height?: number | ((row: any) => number); - render_col?: (arg: { - props: RenderCellProps; - tbl: any; - child: any; - }) => ReactNode; - gen_table?: string; - softdel_type?: string; - paging?: boolean; - cache_row?: boolean; - md?: MDLocal; -}; const w = window as any; const selectCellClassname = css` display: flex; @@ -93,30 +33,31 @@ const selectCellClassname = css` } `; -export const TableList: FC = ({ - 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, -}) => { +export const TableList: FC = (props) => { + const { + 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, + } = props; let mode = _mode; if (mode === "auto") { if (w.isMobile) { @@ -126,286 +67,7 @@ export const TableList: FC = ({ } } - 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(), - cached_row: new WeakMap(), - filtering: "" as ReactNode | string | true, - reloading: null as any, - reload: (arg?: { toast: boolean }) => { - if (local.reloading) return local.reloading; - - local.reloading = new Promise(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 = ( -
- Searching for:
"{filtering.trim()}"
-
- ); - } - } - } - - if (md) { - await new Promise((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( -
- - Failed to load data -
, - { - 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 - >, - }, - soft_delete: { - field: null as any, - }, - }, - ({ setDelayedRender }) => { - setDelayedRender(true); - } - ); + const local = useTableListLocal(props); const reload = local.reload; if (md) { @@ -787,11 +449,9 @@ export const TableList: FC = ({ }} >
{props.row?.__children?.length > 0 && ( <> @@ -1172,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) { const fn = new Function( `return ${get(child, `component.props.${name}.valueBuilt`) || `null`}` diff --git a/comps/list/TableListLocal.tsx b/comps/list/TableListLocal.tsx new file mode 100755 index 0000000..832e909 --- /dev/null +++ b/comps/list/TableListLocal.tsx @@ -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; + orderBy?: Record>; + paging: { take: number; skip: number }; + mode: "count" | "query"; + }) => Promise; + 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; + filter_name: string; + render_row?: (child: any, data: any) => ReactNode; + row_height?: number | ((row: any) => number); + render_col?: (arg: { + props: RenderCellProps; + tbl: any; + child: any; + }) => ReactNode; + gen_table?: string; + softdel_type?: string; + paging?: boolean; + cache_row?: boolean; + md?: MDLocal; +}; +export type TableListLocal = ReturnType; +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(), + cached_row: new WeakMap(), + filtering: "" as ReactNode | string | true, + reloading: null as any, + reload: (arg?: { toast: boolean }) => { + if (local.reloading) return local.reloading; + + local.reloading = new Promise(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 = ( +
+ Searching for:
"{filtering.trim()}"
+
+ ); + } + } + } + + if (md) { + await new Promise((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( +
+ + Failed to load data +
, + { + 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 + >, + }, + soft_delete: { + field: null as any, + }, + }, + ({ setDelayedRender }) => { + setDelayedRender(true); + } + ); + + return local; +}; + +function isAtBottom(currentTarget: HTMLDivElement): boolean { + return ( + currentTarget.scrollTop + 10 >= + currentTarget.scrollHeight - currentTarget.clientHeight + ); +} diff --git a/comps/md/parts/MDMaster.tsx b/comps/md/parts/MDMaster.tsx index 5f0e39d..030f559 100755 --- a/comps/md/parts/MDMaster.tsx +++ b/comps/md/parts/MDMaster.tsx @@ -33,10 +33,6 @@ export const MDRenderMaster: FC<{ size: width, min_size: min_width, }); - // if (md.panel) { - // md.panel.min_size = min_width; - // md.panel.size = width; - // } } }, Object.values(md.deps || {}) || []); diff --git a/comps/md/utils/typings.ts b/comps/md/utils/typings.ts index c81a8dd..8de750b 100755 --- a/comps/md/utils/typings.ts +++ b/comps/md/utils/typings.ts @@ -1,7 +1,8 @@ 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 { FMLocal } from "lib/comps/form/typings"; +import { TableListLocal } from "lib/comps/list/TableListLocal"; +import { GFCol } from "lib/gen/utils"; import { ReactNode } from "react"; type ID_MASTER_DETAIL = string; @@ -58,7 +59,7 @@ export type MDLocalInternal = { master: { reload: (arg?: { toast: boolean }) => void; render: () => void; - list?: any; + list?: TableListLocal; pk?: string; }; params: { @@ -68,6 +69,7 @@ export type MDLocalInternal = { parse: () => void; apply: () => void; }; + pk?: GFCol; props: { mode: "full" | "h-split" | "v-split"; diff --git a/modules.json b/modules.json index b0dd4a3..0744f8c 100755 --- a/modules.json +++ b/modules.json @@ -10,6 +10,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.1", "input-otp": "^1.4.1", "@radix-ui/react-label": "^2.0.2", + "input-otp": "^1.4.1", "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-progress": "^1.1.0", diff --git a/utils/baseurl.ts b/utils/baseurl.ts index a7fd6be..1db3202 100755 --- a/utils/baseurl.ts +++ b/utils/baseurl.ts @@ -4,6 +4,7 @@ export const baseurl = (url: string) => { location.host === "localhost:4550" ) { const id_site = location.pathname.split("/")[2]; + if (url.startsWith(`/prod/${id_site}`)) return url; if (url.startsWith("/")) return `/prod/${id_site}${url}`; else return `/prod/${id_site}/${url}`;