From ec2c0d55e5c9259a84e6a3f8d0578c2bc4a9bf9b Mon Sep 17 00:00:00 2001 From: rizrmd Date: Sun, 24 Mar 2024 01:26:46 -0700 Subject: [PATCH] wip fix --- comps/form/Dropdown/index.tsx | 20 ++-- comps/form/Dropdown/relation.tsx | 195 +++++++++++++++++++++++++++++++ comps/form/Field.tsx | 28 ++++- 3 files changed, 230 insertions(+), 13 deletions(-) create mode 100755 comps/form/Dropdown/relation.tsx diff --git a/comps/form/Dropdown/index.tsx b/comps/form/Dropdown/index.tsx index 84f093b..98d00f2 100755 --- a/comps/form/Dropdown/index.tsx +++ b/comps/form/Dropdown/index.tsx @@ -1,20 +1,20 @@ import { Popover } from "@/comps/custom/Popover"; import { Input } from "@/comps/ui/input"; +import { Skeleton } from "@/comps/ui/skeleton"; import { useLocal } from "@/utils/use-local"; import { ChevronDown } from "lucide-react"; import { FC, useEffect } from "react"; -import type { ControllerRenderProps, FieldValues } from "react-hook-form"; import { FieldListItem, FieldOptions } from "../type"; import { FormHook } from "../utils/utils"; -import { Skeleton } from "@/comps/ui/skeleton"; -export const Dropdown: FC< - ControllerRenderProps & { - options: FieldOptions; - form?: FormHook; - name: string; - } -> = ({ value, options, form, name }) => { +type DropdownProps = { + value: string; + options: FieldOptions; + form?: FormHook; + name: string; +}; + +export const Dropdown: FC = ({ value, options, form, name }) => { const local = useLocal({ status: "loading" as "loading" | "ready", open: false, @@ -62,7 +62,7 @@ export const Dropdown: FC< if (local.filter) { filtered = local.list.filter((e) => { - if (e.value.toLowerCase().includes(local.filter)) return true; + if (e.label.toLowerCase().includes(local.filter)) return true; return false; }); } diff --git a/comps/form/Dropdown/relation.tsx b/comps/form/Dropdown/relation.tsx new file mode 100755 index 0000000..6cd4540 --- /dev/null +++ b/comps/form/Dropdown/relation.tsx @@ -0,0 +1,195 @@ +import { Popover } from "@/comps/custom/Popover"; +import { Input } from "@/comps/ui/input"; +import { Skeleton } from "@/comps/ui/skeleton"; +import { useLocal } from "@/utils/use-local"; +import { ChevronDown } from "lucide-react"; +import { FC, useEffect } from "react"; +import { FieldListItem, FieldOptions } from "../type"; +import { FormHook } from "../utils/utils"; + +type RelationProps = { + value: string; + relation: { + table: string; + fields: string[]; + query: () => Promise; + }; + form?: FormHook; + name: string; +}; + +export const Relation: FC = ({ + relation, + value, + form, + name, +}) => { + const local = useLocal({ + status: "loading" as "loading" | "ready", + open: false, + ref: { input: null as null | HTMLInputElement }, + list: [] as FieldListItem[], + input: "", + label: "", + filter: "", + pk: "", + }); + + useEffect(() => { + (async () => { + if (form) { + local.status = "loading"; + local.render(); + const table_fn = (db as any)[relation.table]; + const select = {} as any; + local.pk = ""; + for (const f of relation.fields) { + if (f.startsWith("::")) { + select[f.substring(2)] = true; + local.pk = f.substring(2); + } else { + select[f] = true; + } + } + let q = {}; + + if (typeof relation.query === "function") { + q = await relation.query(); + } + + const list = await table_fn.findMany({ select, ...q }); + if (Array.isArray(list)) { + local.list = list.map((item: any) => { + let label = []; + for (const [k, v] of Object.entries(item)) { + if (k !== local.pk) label.push(v); + } + return { value: item[local.pk], label: label.join(" - ") }; + }); + } + + const found = local.list.find((e) => e.value === value); + if (found) { + local.label = found.label; + } + + local.status = "ready"; + local.render(); + } + })(); + }, [relation]); + + let filtered = local.list; + + if (local.filter) { + filtered = local.list.filter((e) => { + if (e.label.toLowerCase().includes(local.filter)) return true; + return false; + }); + } + + return ( + { + local.open = false; + local.render(); + }} + arrow={false} + className={cx("c-rounded-sm c-bg-white")} + content={ +
+ {local.status === "loading" && ( + <> +
+ + +
+ + )} + {local.status === "ready" && ( + <> + {filtered.map((item, idx) => { + return ( +
0 && "c-border-t", + idx === 0 && "c-rounded-t-sm", + idx === local.list.length - 1 && "c-rounded-b-sm" + )} + onClick={() => { + if (form) { + local.open = false; + form.hook.setValue(name, item.value); + form.render(); + } + }} + > + {item.label} +
+ ); + })} + + )} +
+ } + > +
{ + local.open = true; + local.input = local.label; + local.filter = ""; + local.render(); + setTimeout(() => { + local.ref.input?.focus(); + }); + }} + > +
+ +
+ { + local.input = e.currentTarget.value; + local.filter = local.input.toLowerCase(); + local.render(); + }} + ref={(el) => { + local.ref.input = el; + }} + type="text" + /> + {!local.open && ( +
+ {local.label} +
+ )} +
+
+ ); +}; diff --git a/comps/form/Field.tsx b/comps/form/Field.tsx index 677230a..1999d56 100755 --- a/comps/form/Field.tsx +++ b/comps/form/Field.tsx @@ -19,6 +19,7 @@ import { SliderOptions } from "./Slider/types"; import { FormHook, modify } from "./utils/utils"; import { Dropdown } from "./Dropdown"; import { FieldOptions } from "./type"; +import { Relation } from "./Dropdown/relation"; export const Field: FC<{ name: string; @@ -30,6 +31,7 @@ export const Field: FC<{ | "number" | "textarea" | "dropdown" + | "relation" | "password" | "radio" | "date" @@ -50,6 +52,9 @@ export const Field: FC<{ label_alt: | ReactNode | FC<{ modify: typeof modify; data: any; current_name: string }>; + rel_table: string; + rel_fields: string[]; + rel_query: () => any; }> = ({ name, form, @@ -67,6 +72,9 @@ export const Field: FC<{ child, placeholder, label_alt, + rel_fields, + rel_table, + rel_query, }) => { const value = form?.hook.getValues()[name]; const local = useLocal({ @@ -184,10 +192,11 @@ export const Field: FC<{ {["text", "number", "password"].includes(type) && (suffix !== "" ? ( -
+
- + {suffix || "-"}
@@ -221,10 +230,23 @@ export const Field: FC<{ {type === "dropdown" && ( + )} + + {type === "relation" && ( + )}