From 4433f5fcd1b4de2a0942b7c4071970e4a73b65ac Mon Sep 17 00:00:00 2001 From: rizrmd Date: Thu, 30 May 2024 21:20:03 -0700 Subject: [PATCH] wip fix filter --- comps/filter/FieldModifier.tsx | 32 ++++ comps/filter/Filter.tsx | 159 ---------------- comps/filter/FilterContent.tsx | 67 +++++++ comps/filter/FilterField.tsx | 74 ++++++++ comps/filter/Label.tsx | 29 --- comps/filter/MasterFilter.tsx | 103 +++++++++++ comps/filter/filter.css | 26 --- comps/filter/utils/filter-where.ts | 41 +++++ comps/filter/utils/types.tsx | 33 ++++ comps/form/base/BaseField.tsx | 116 ++++++++++++ comps/form/base/BaseForm.tsx | 111 ++++++++++++ comps/form/base/types.ts | 29 +++ comps/form/field/type/TypeDropdown.tsx | 2 - comps/form/field/type/TypeText.tsx | 14 +- comps/form/typings.ts | 10 + comps/list/TableList.tsx | 93 ++++++---- comps/md/MasterDetail.tsx | 3 +- comps/md/gen/form/fields.ts | 157 ++++++++++++++++ comps/md/gen/form/on_load.ts | 66 +++++++ comps/md/gen/gen-form.ts | 75 ++++++++ comps/md/gen/gen-table-list.ts | 129 +++++++++++++ comps/md/gen/md-form.ts | 122 +++++++++++++ comps/md/gen/md-list.ts | 59 +++--- comps/md/gen/md-table-list.ts | 241 ------------------------- comps/md/gen/tbl-list/on_submit.ts | 85 +++++++++ comps/md/parts/MDMaster.tsx | 13 +- comps/md/utils/editor-init.tsx | 1 - exports.ts | 10 +- gen/utils.ts | 3 + preset/login/Login.tsx | 9 +- preset/login/utils/user.ts | 2 + preset/menu/Layout.tsx | 2 +- utils/format-value.tsx | 25 +-- utils/parse-gen-fields.ts | 33 ++++ 34 files changed, 1413 insertions(+), 561 deletions(-) create mode 100755 comps/filter/FieldModifier.tsx delete mode 100755 comps/filter/Filter.tsx create mode 100755 comps/filter/FilterContent.tsx create mode 100755 comps/filter/FilterField.tsx delete mode 100755 comps/filter/Label.tsx create mode 100755 comps/filter/MasterFilter.tsx delete mode 100755 comps/filter/filter.css create mode 100755 comps/filter/utils/filter-where.ts create mode 100755 comps/filter/utils/types.tsx create mode 100755 comps/form/base/BaseField.tsx create mode 100755 comps/form/base/BaseForm.tsx create mode 100755 comps/form/base/types.ts create mode 100755 comps/md/gen/form/fields.ts create mode 100755 comps/md/gen/form/on_load.ts create mode 100755 comps/md/gen/gen-form.ts create mode 100755 comps/md/gen/gen-table-list.ts create mode 100755 comps/md/gen/md-form.ts delete mode 100755 comps/md/gen/md-table-list.ts create mode 100755 comps/md/gen/tbl-list/on_submit.ts create mode 100755 utils/parse-gen-fields.ts diff --git a/comps/filter/FieldModifier.tsx b/comps/filter/FieldModifier.tsx new file mode 100755 index 0000000..fdf67f1 --- /dev/null +++ b/comps/filter/FieldModifier.tsx @@ -0,0 +1,32 @@ +import capitalize from "lodash.capitalize"; +import { FC } from "react"; +import { FilterFieldType, modifiers } from "./utils/types"; + +export const FieldModifier: FC<{ + type: FilterFieldType; + modifier: string; + onChange: (modifier: string) => void; +}> = ({ type, modifier, onChange }) => { + if (!modifier && modifiers[type]) { + onChange(Object.keys(modifiers[type])[0]); + } + + return ( + + ); +}; diff --git a/comps/filter/Filter.tsx b/comps/filter/Filter.tsx deleted file mode 100755 index 31f7ac4..0000000 --- a/comps/filter/Filter.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { useLocal } from "@/utils/use-local"; -import get from "lodash.get"; -import { FC, ReactNode, useEffect, useRef } from "react"; -import { FMLocal } from "../form/Form"; -import { createPortal, render } from "react-dom"; -import { Label } from "@/comps/ui/label"; -import { Input } from "@/comps/ui/input"; - -type FilterPosition = 'regular' | 'inline' | 'popup'; - -type FilterForm = { - gen_fields: GenField[]; - gen_table: string; - name: string; - value: any; - position: FilterPosition; - children?: ReactNode; - onClose?: () => void; -}; - -type GenField = { - name: string, - is_pk: boolean, - type: string, - optional: boolean -}; - -export const MasterFilter: FC = ({ gen_fields, gen_table, name, value, position, children, onClose }): ReactNode => { - const local = useLocal({ - data: [] as any[], - columns: [] as string[], - fields: [] as GenField[], - tableName: "", - isGenerated: false, - // fm: {} as FMLocal, - argInit: {} as { fm: FMLocal; submit: any; reload: any }, - argOnLoad: {} as { arg: { fm: FMLocal } }, - argOnSubmit: {} as { - arg: { - fm: FMLocal; - form: any; - error: any; - } - } - }); - useEffect(() => { - if (!isEditor) { - const w = window as any; - if (typeof w["prasi_filter"] !== "object") w["prasi_filter"] = {}; - if (typeof w["prasi_filter"][name] !== "object") - w["prasi_filter"][name] = {}; - const val = value(); - w["prasi_filter"][name] = { - ...w["prasi_filter"][name], - ...val - }; - w.prasiContext.render(); - } - }, []) - - const renderContent = (): ReactNode => ( -
-

Filter

-
- {local.fields.map((field) => ( -
- {field.type === 'varchar' && ( -
- - -
- )} - {field.type === "number" && ( -
- - -
- )} -
- ))} -
-
- ); - - const generateFilter = () => { - local.isGenerated = true; - local.tableName = gen_table; - gen_fields.forEach((data: any) => { - local.fields.push(JSON.parse(data)); - }); - local.render(); - console.log('tableName', local.tableName); - console.log('fields', local.fields); - }; - - if (position === 'popup') { - let popup = document.querySelector(".main-content-preview > .portal"); - if (!popup) { - popup = document.createElement("div"); - popup.classList.add("portal"); - - const main = document.querySelector(".main-content-preview"); - if (main) { - main.appendChild(popup); - } - } - return ( - <> - {createPortal( -
- {renderContent()} -
, - popup - )} - - ); - } - - if (local.isGenerated) { - return renderContent(); - } else { - return ( -
- -
- ) - } -}; diff --git a/comps/filter/FilterContent.tsx b/comps/filter/FilterContent.tsx new file mode 100755 index 0000000..3049523 --- /dev/null +++ b/comps/filter/FilterContent.tsx @@ -0,0 +1,67 @@ +import { FC } from "react"; +import { BaseForm } from "../form/base/BaseForm"; +import { FilterLocal } from "./utils/types"; +import { useLocal } from "lib/utils/use-local"; + +export const FilterContent: FC<{ + mode: string; + filter: FilterLocal; + PassProp: any; + child: any; + _item: PrasiItem; +}> = ({ mode, filter, PassProp, child, _item }) => { + const internal = useLocal({}); + return ( +
+ { + console.log("skrg nyubmit"); + }} + render={internal.render} + > + {(form) => { + filter.form = form; + return ( + <> + {!!(PassProp && child) && ( + {child} + )} + + ); + }} + + + +
+ ); +}; diff --git a/comps/filter/FilterField.tsx b/comps/filter/FilterField.tsx new file mode 100755 index 0000000..1154eb5 --- /dev/null +++ b/comps/filter/FilterField.tsx @@ -0,0 +1,74 @@ +import { FC, useEffect } from "react"; +import { BaseField } from "../form/base/BaseField"; +import { FilterLocal, filter_window } from "./utils/types"; +import { FieldTypeText } from "../form/field/type/TypeText"; +import { FieldModifier } from "./FieldModifier"; +import { useLocal } from "lib/utils/use-local"; + +export const FilterField: FC<{ + filter: FilterLocal; + name?: string; + label?: string; + type: "text" | "number" | "boolean"; +}> = ({ filter, name, label, type }) => { + const internal = useLocal({ render_timeout: null as any }); + if (!name) return <>No Name; + if (!filter.form) return
Loading...
; + + filter.types[name] = type; + + useEffect(() => { + clearTimeout(internal.render_timeout); + internal.render_timeout = setTimeout(() => { + filter_window.prasiContext.render(); + }, 500); + }, [filter.form?.data[name]]); + + return ( + ( + { + filter.modifiers[name] = modifier; + filter.render(); + filter_window.prasiContext.render(); + }} + modifier={filter.modifiers[name]} + type={type} + /> + ), + })} + > + {(field) => ( + <> + {type === "text" && ( + + )} + {type === "number" && ( + + )} + + )} + + ); +}; diff --git a/comps/filter/Label.tsx b/comps/filter/Label.tsx deleted file mode 100755 index 94ba4bc..0000000 --- a/comps/filter/Label.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { FC, ReactNode } from "react"; - -export const Label: FC<{ children: ReactNode; text: string }> = ({ - children, - text, -}) => { - return ( - - ); -}; \ No newline at end of file diff --git a/comps/filter/MasterFilter.tsx b/comps/filter/MasterFilter.tsx new file mode 100755 index 0000000..915a264 --- /dev/null +++ b/comps/filter/MasterFilter.tsx @@ -0,0 +1,103 @@ +import { useLocal } from "@/utils/use-local"; +import { FC, ReactNode, useEffect } from "react"; +import { createPortal } from "react-dom"; +import { GenField } from "../form/typings"; +import { default_filter_local, filter_window } from "./utils/types"; +import { FilterContent } from "./FilterContent"; +import { getPathname } from "lib/utils/pathname"; + +type FilterMode = "regular" | "inline" | "popup"; + +type FilterProps = { + gen_fields: GenField[]; + gen_table: string; + name: string; + value: any; + mode: FilterMode; + children?: ReactNode; + onClose?: () => void; + PassProp: any; + child: any; + _item: PrasiItem; +}; + +export const MasterFilter: FC = ({ + gen_fields, + gen_table, + name, + value, + mode, + PassProp, + child, + onClose, + _item, +}): ReactNode => { + const filter = useLocal({ ...default_filter_local }); + filter.name = name; + + if (!isEditor) { + if (!filter_window.prasi_filter) { + filter_window.prasi_filter = {}; + } + const pf = filter_window.prasi_filter; + if (pf) { + const pathname = getPathname(); + if (!pf[pathname]) pf[pathname] = {}; + if (!pf[pathname][name]) pf[pathname][name] = {}; + pf[pathname][name][_item.id] = filter; + } + } + + if (mode === "popup") { + let popup = document.querySelector(".main-content-preview > .portal"); + if (!popup) { + popup = document.createElement("div"); + popup.classList.add("portal"); + + const main = document.querySelector(".main-content-preview"); + if (main) { + main.appendChild(popup); + } + } + return ( + <> + {createPortal( +
+ +
, + popup + )} + + ); + } + + return ( + + ); +}; diff --git a/comps/filter/filter.css b/comps/filter/filter.css deleted file mode 100755 index 03a051d..0000000 --- a/comps/filter/filter.css +++ /dev/null @@ -1,26 +0,0 @@ -.filter-content { - padding: 1rem; - border: 1px solid #ccc; - border-radius: 4px; - background: #fff; -} - -.filter-regular { - width: 250px; - /* Styles specific to sidebar */ -} - -.filter-inline { - display: flex; - align-items: center; - /* Styles specific to topbar */ -} - -.filter-popup { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1000; - /* Styles specific to popup */ -} diff --git a/comps/filter/utils/filter-where.ts b/comps/filter/utils/filter-where.ts new file mode 100755 index 0000000..515b326 --- /dev/null +++ b/comps/filter/utils/filter-where.ts @@ -0,0 +1,41 @@ +import { getPathname } from "lib/utils/pathname"; +import { filter_window } from "./types"; + +export const filterWhere = (filter_name: string) => { + const pf = filter_window.prasi_filter?.[getPathname()]?.[filter_name]; + const where: any = {}; + const AND: any[] = []; + + if (pf) { + for (const [k, filter] of Object.entries(pf)) { + for (const [name, value] of Object.entries(filter.data)) { + const type = filter.types[name]; + const modifier = filter.modifiers[name]; + + switch (type) { + case "text": + { + if (modifier === "contains") + where[name] = { + contains: value, + mode: "insensitive", + }; + } + break; + case "date": { + let is_value_valid = false; + // TODO: pastikan value bisa diparse pakai any-date-parser + if (is_value_valid) { + if (modifier === "between") { + AND.push({ [name]: { gt: value } }); + AND.push({ [name]: { lt: value } }); + } + } + } + } + } + } + if (AND.length > 0) where.AND = AND; + } + return where; +}; diff --git a/comps/filter/utils/types.tsx b/comps/filter/utils/types.tsx new file mode 100755 index 0000000..b28c501 --- /dev/null +++ b/comps/filter/utils/types.tsx @@ -0,0 +1,33 @@ +import { BaseFormLocal } from "../../form/base/types"; +import { GenField } from "../../form/typings"; + +export type FilterFieldType = "text" | "number" | "boolean" | "date"; +export const default_filter_local = { + data: {} as any, + columns: [] as string[], + fields: [] as GenField[], + tableName: "", + form: null as null | BaseFormLocal, + modifiers: {} as Record, + types: {} as Record, + name: "", +}; + +export const modifiers = { + text: { contains: "Contains", ends_with: "Ends With" }, + boolean: {}, + number: {}, + date: { + between: "Between", + }, +}; +export type FilterModifier = typeof modifiers; + +export type FilterLocal = typeof default_filter_local & { render: () => void }; + +export const filter_window = window as unknown as { + prasi_filter: Record>>; + prasiContext: { + render: () => void; + }; +}; diff --git a/comps/form/base/BaseField.tsx b/comps/form/base/BaseField.tsx new file mode 100755 index 0000000..5fd092a --- /dev/null +++ b/comps/form/base/BaseField.tsx @@ -0,0 +1,116 @@ +import { ReactNode } from "react"; +import { FMLocal, FieldLocal, FieldProp } from "../typings"; +import { Label } from "../field/Label"; +import { FieldLoading } from "../field/raw/FieldLoading"; + +export const BaseField = (prop: { + field: FieldLocal; + fm: FMLocal; + arg: FieldProp; + children: (arg: { + field: FieldLocal; + fm: FMLocal; + arg: FieldProp; + }) => ReactNode; +}) => { + const { field, fm, arg } = prop; + const w = field.width; + const mode = fm.props.label_mode; + const prefix = + typeof field.prefix === "function" + ? field.prefix() + : typeof field.prefix === "string" + ? field.prefix + : null; + const suffix = + typeof field.suffix === "function" + ? field.suffix() + : typeof field.suffix === "string" + ? field.prefix + : null; + const name = field.name; + const errors = fm.error.get(name); + let type_field = typeof arg.type === "function" ? arg.type() : arg.type; // tipe field + + return ( +