diff --git a/comps/custom/Breadcrumb.tsx b/comps/custom/Breadcrumb.tsx new file mode 100755 index 0000000..544dccf --- /dev/null +++ b/comps/custom/Breadcrumb.tsx @@ -0,0 +1,92 @@ +import { useLocal } from "@/utils/use-local"; +import { FC, ReactNode, useEffect } from "react"; +import { Skeleton } from "../ui/skeleton"; + +type BreadcrumbProps = { + on_load: () => Promise; + props: any; +}; + +export const Breadcrumb: FC = (_arg) => { + const { on_load } = _arg; + + const local = useLocal({ + list: [] as { label: string; url: string }[], + status: "init" as "init" | "loading" | "ready", + params: {}, + }); + + useEffect(() => { + (async () => { + if (local.status === "init") { + local.status = "loading"; + local.render(); + + local.list = await on_load(); + + local.status = "ready"; + local.render(); + } + })(); + }, [on_load]); + + return ( +
+ {local.status !== "ready" ? ( + + ) : ( + <> + {local.list === null ? ( + <> +

+ Dummy +

+ + ) : ( + (local.list || []).map((item, index): ReactNode => { + const lastIndex = local.list.length - 1; + + return ( + <> + {index === lastIndex ? ( +

+ {item?.label} +

+ ) : ( +

{ + navigate(item?.url); + }} + > + {item?.label} +

+ )} + + {index !== lastIndex && ( +
+ + + +
+ )} + + ); + }) + )} + + )} +
+ ); +}; diff --git a/comps/custom/Card.tsx b/comps/custom/Card.tsx index 1b4ec61..6a67d65 100755 --- a/comps/custom/Card.tsx +++ b/comps/custom/Card.tsx @@ -6,7 +6,9 @@ import { Skeleton } from "../ui/skeleton"; export const Card: FC<{ title: { left: ReactNode; right: ReactNode }; - desc: (() => Promise) | ReactNode; + desc: + | ((arg: { setOnClick: (fn: any) => void }) => Promise) + | ReactNode; value: (() => Promise) | ReactNode; }> = ({ title, desc, value }) => { const local = useLocal({ @@ -14,6 +16,7 @@ export const Card: FC<{ desc: "" as any, status_value: "init" as "init" | "loading" | "ready", status_desc: "init" as "init" | "loading" | "ready", + onClick: null as null | (() => void), }); useEffect(() => { @@ -38,7 +41,12 @@ export const Card: FC<{ if (!!desc && typeof desc === "function") { local.status_desc = "loading"; local.render(); - const result = desc(); + const result = desc({ + setOnClick: (fn) => { + local.onClick = fn; + local.render(); + }, + }); if (typeof result === "object" && result instanceof Promise) { result.then((val) => { local.desc = val; @@ -71,7 +79,14 @@ export const Card: FC<{ } return ( - + { + if (local.onClick) { + local.onClick(); + } + }} + >
{!!title && (title.left || title.right) && (
diff --git a/comps/custom/Detail.tsx b/comps/custom/Detail.tsx index ea763b3..c12976a 100755 --- a/comps/custom/Detail.tsx +++ b/comps/custom/Detail.tsx @@ -6,7 +6,10 @@ import { Skeleton } from "../ui/skeleton"; export const Detail: FC<{ detail: (item: any) => Record; - on_load: (arg: { params: any }) => Promise; + on_load: (arg: { + params: any; + bind: (fn: (on_load: any) => void) => void; + }) => Promise; mode: "standard" | "compact" | "inline"; }> = ({ detail, mode, on_load }) => { const local = useLocal({ @@ -15,6 +18,7 @@ export const Detail: FC<{ pathname: "", mode: mode, on_load, + bound: false, }); if (!isEditor) { @@ -39,7 +43,24 @@ export const Detail: FC<{ } local.render(); - const res = on_load({ params: {} }); + const res = on_load({ + params: {}, + bind: (fn) => { + if (!local.bound) { + local.bound = true; + local.render(); + + fn(async () => { + local.status = "loading"; + local.render(); + const item = await on_load({} as any); + local.detail = detail(item); + local.status = "ready"; + local.render(); + }); + } + }, + }); if (typeof res === "object" && res instanceof Promise) { res.then((item) => { local.detail = detail(item); @@ -70,7 +91,34 @@ export const Detail: FC<{ "c-flex c-relative items-stretch", mode === "inline" ? "c-flex-row c-my-2" - : "c-flex-col c-flex-1 c-w-full c-h-full " + : "c-flex-col c-flex-1 c-w-full c-h-full ", + isDesktop && + entries.length > 3 && + mode === "compact" && + css` + flex-direction: row !important; + flex-wrap: wrap !important; + border: 1px solid #ddd; + border-radius: 10px; + margin-top: 10px; + margin-bottom: 10px; + + &::before { + content: " "; + position: absolute; + left: 49%; + bottom: 0px; + top: 0px; + border-right: 1px solid #ddd; + } + + > div { + border-top: 0px; + padding-right: 10px; + padding-left: 10px; + width: 49% !important; + } + ` )} > {entries.map(([name, data], idx) => { @@ -90,7 +138,7 @@ export const Detail: FC<{ if (mode === "standard") { return ( -
+
{label}
void); + props: any; + actions: () => Promise<[string, string | (() => void), ReactNode][]>; + action_mode: "auto" | "buttons" | "sub-header" | "dropdown"; + edit_url: string; +}> = ({ text, back_url, props, edit_url, actions, action_mode }) => { + const local = useLocal({ + loading: false, + actions: [] as [string, string | (() => void), ReactNode][], + }); + + useEffect(() => { + (async () => { + if (typeof actions === "function") { + local.loading = true; + local.render(); + const res_actions = await actions(); + if (Array.isArray(res_actions)) { + local.actions = res_actions; + preload( + res_actions + .filter((e) => typeof e[1] === "string") + .map((e) => e[1]) as string[] + ); + } + local.loading = false; + local.render(); + } + })(); + }, [actions, action_mode]); + + const back = () => { + if (isEditor) return; + if (typeof back_url === "function") { + const url = back_url(); + if (typeof url === "string") { + if (url === "back") { + history.back(); + return; + } + navigate(url); + } + } else { + if (back_url === "back") { + history.back(); + return; + } + navigate(back_url); + } + }; + return ( +
+ {typeof back_url === "string" && preload([back_url])} + {back_url && ( +
+ + + +
+ )} +
+
+ {text} +
+
+ {local.loading && ( + + )} + {!local.loading && ( + <> + {local.actions.map((e) => { + const [label, to, icon] = e; + return ( +
{ + if (isEditor) return; + if (typeof to === "string") { + navigate(to); + } else if (typeof to === "function") { + to(); + } + }} + > +
+ {icon} +
+
{label}
+
+ ); + })} + + )} + + {/* {edit_url && edit_url !== "" && params.id !== "_" && ( +
{ + if (isEditor) return; + if (typeof edit_url === "string") { + navigate(edit_url); + } + }} + > + + + + + +
+ )} */} +
+
+
+ ); +}; diff --git a/comps/custom/Tab.tsx b/comps/custom/Tab.tsx index d38a303..3b96797 100755 --- a/comps/custom/Tab.tsx +++ b/comps/custom/Tab.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, useEffect } from "react"; import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; import { useLocal } from "@/utils/use-local"; @@ -64,6 +64,17 @@ export const Tab: FC<{ } } + if (!isEditor) { + useEffect(() => { + if (local.mode === "hash") { + local.activeIndex = location.hash.substring(1); + if (!parseInt(local.activeIndex)) { + local.activeIndex = "0"; + } + local.render(); + } + }, [location.hash]); + } return (
; +}; + +export const Table: FC = (_args) => { + const { map_val } = _args; + + const local = useLocal({ + list: [ + { + name: "test1", + }, + { + name: "test2", + }, + ] as { name: string }[], + status: "init" as "init" | "loading" | "ready", + }); + + // useEffect(() => { + // (async () => { + // if (local.status === "init") { + // local.status = "loading"; + // local.render(); + + // local.list = await map_val; + // local.render(); + + // local.status = "ready"; + // local.render(); + // } + // })(); + // }, [map_val]); + + console.log(local.list, "tes"); + + return ( +
+ + + + + + + + + + {!!local.list && + local.list.map((item, index) => ( + + + + + + ))} + +
NomorHeader 1Action
+ {index + 1} + {item.name} + + +
+
+ ); +}; diff --git a/comps/form/Field.tsx b/comps/form/Field.tsx index d0f94d4..8ed48e1 100755 --- a/comps/form/Field.tsx +++ b/comps/form/Field.tsx @@ -6,31 +6,25 @@ import { FormLabel, FormMessage, } from "@/comps/ui/form"; +import { useLocal } from "@/utils/use-local"; +import autosize from "autosize"; import { FC, useEffect, useRef } from "react"; import { UseFormReturn } from "react-hook-form"; -import { Input } from "../ui/input"; -import { useLocal } from "@/utils/use-local"; import { Button } from "../ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "@/comps/ui/popover"; -import { format } from "date-fns"; -import { Calendar } from "@/comps/ui/calendar"; -import { Calendar as CalendarIcon } from "lucide-react"; -import { PopUpDropdown } from "./PopUpDropdown"; -import { ButtonOptions } from "./ButtonOptions"; +import { Input } from "../ui/input"; import { Textarea } from "../ui/textarea"; -import autosize from "autosize"; -import { InputMoney } from "./InputMoney"; +import { ButtonOptions } from "./ButtonOptions"; import { Date } from "./Date"; import { Datetime } from "./Datetime"; -import { Slider } from "@radix-ui/react-slider"; +import { InputMoney } from "./InputMoney"; +import { PopUpDropdown } from "./PopUpDropdown"; import { SliderOptions } from "./Slider/types"; -import { cn } from "@/utils"; export const Field: FC<{ name: string; label: string; desc?: string; - form: { hook: UseFormReturn; render: () => void }; + form?: { hook: UseFormReturn; render: () => void }; type: | "text" | "textarea" @@ -46,7 +40,7 @@ export const Field: FC<{ options: () => Promise<{ value: string; label: string }[]>; slider_options: () => Promise; }> = ({ name, form, desc, label, type, required, options, slider_options }) => { - const value = form.hook.getValues()[name]; + const value = form?.hook.getValues()[name]; const local = useLocal({ dropdown: { popup: false, @@ -104,14 +98,14 @@ export const Field: FC<{ local.render(); }} on_select={(value: any) => { - form.hook.setValue(name, value); + form?.hook.setValue(name, value); }} title={label} options={options} /> )} ( @@ -138,7 +132,7 @@ export const Field: FC<{ value, local.slider.opt ); - form.hook.setValue(name, value); + form?.hook.setValue(name, value); local.render(); }} value={local.slider.value} @@ -180,7 +174,7 @@ export const Field: FC<{ {type === "date" && ( { - form.hook.setValue(name, value); + form?.hook.setValue(name, value); }} /> )} @@ -188,7 +182,7 @@ export const Field: FC<{ {type === "datetime" && ( { - form.hook.setValue(name, value); + form?.hook.setValue(name, value); }} /> )} @@ -198,7 +192,7 @@ export const Field: FC<{ options={options} value={field.value} on_select={(value: any) => { - form.hook.setValue(name, value); + form?.hook.setValue(name, value); }} /> )} @@ -207,7 +201,7 @@ export const Field: FC<{ { - form.hook.setValue(name, value); + form?.hook.setValue(name, value); }} /> )} diff --git a/comps/form/Form.tsx b/comps/form/Form.tsx index 8902599..e633777 100755 --- a/comps/form/Form.tsx +++ b/comps/form/Form.tsx @@ -1,4 +1,5 @@ import { Form as FForm } from "@/comps/ui/form"; +import { useLocal } from "@/utils/use-local"; import { FC } from "react"; import { useForm } from "react-hook-form"; @@ -8,13 +9,22 @@ export const Form: FC<{ body: any; form: { hook: any; render: () => void }; PassProp: any; -}> = ({ on_load, body, form, PassProp, on_submit }) => { + layout: "auto" | "1-col" | "2-col"; +}> = ({ on_load, body, form, PassProp, on_submit, layout: _layout }) => { const form_hook = useForm({ defaultValues: on_load, }); + const local = useLocal({ + el: null as any, + layout: "unknown" as "unknown" | "2-col" | "1-col", + }); + form.hook = form_hook; + let layout = _layout || "auto"; + if (layout !== "auto") local.layout = layout; + return (
-
+
{ + if (el && layout === "auto" && local.layout === "unknown") { + let cur: any = el; + let i = 0; + while ( + cur.parentNode && + cur.getBoundingClientRect().width === 0 + ) { + cur = cur.parentNode; + i++; + if (i > 10) { + break; + } + } + + if (cur.getBoundingClientRect().width < 500) { + local.layout = "1-col"; + } else { + local.layout = "2-col"; + } + local.render(); + } + }} + className={cx( + local.layout === "unknown" && "c-hidden", + local.layout === "2-col" && + css` + > div { + flex-direction: row; + flex-wrap: wrap; + > div { + width: 50%; + } + } + ` + )} + > { on_submit({ form: form.hook.getValues(), error: {} }); diff --git a/comps/icon.tsx b/comps/icon.tsx index 007bc44..a443a9d 100755 --- a/comps/icon.tsx +++ b/comps/icon.tsx @@ -1,4 +1,48 @@ -import { Bell, Calendar, ChevronRight, UsersRound, ChevronDown, Contact2, Drill, HelpCircle, Home, LayoutList, ListChecks, ListTodo, PenBoxIcon, QrCode, UserCog, Workflow, Settings, Newspaper, PanelRightOpen, PanelLeftOpen, LayoutDashboard, Building, Box, Package, Blocks, Megaphone, MousePointerSquare, Siren, Ship, HeartHandshake, StickyNote, Volume2, Wallet, FileDown, Menu, X, LogOut, Rocket, BookOpen } from "lucide-react"; +import { + Bell, + Calendar, + ChevronRight, + UsersRound, + ChevronDown, + Contact2, + Drill, + HelpCircle, + Home, + LayoutList, + ListChecks, + ListTodo, + PenBoxIcon, + QrCode, + UserCog, + Workflow, + Settings, + Newspaper, + PanelRightOpen, + PanelLeftOpen, + LayoutDashboard, + Building, + Box, + Package, + Blocks, + Megaphone, + MousePointerSquare, + Siren, + Ship, + HeartHandshake, + StickyNote, + Volume2, + Wallet, + FileDown, + Menu, + X, + LogOut, + Rocket, + BookOpen, + Pencil, + Plus, + Delete, + Edit, +} from "lucide-react"; export const icon = { home: , @@ -14,8 +58,10 @@ export const icon = { manage_user: , notification: , profile: , + pencil: , + plus: , bell: , - bellSmall: , + bellSmall: , bellSmallTransparent: , help: , user: , @@ -53,6 +99,20 @@ export const icon = { releaseSmallTransparent: , newsSmall: , newsSmallTransparent: , + scan: ( + + + + ), + delete: , + update: }; - - diff --git a/comps/list/List.tsx b/comps/list/List.tsx index c9ad396..5fd5344 100755 --- a/comps/list/List.tsx +++ b/comps/list/List.tsx @@ -24,7 +24,7 @@ export const List: FC = (_arg) => { list: [] as any[], }); - if (isEditor) { + if (isEditor || typeof on_load !== 'function') { return ; } @@ -35,14 +35,16 @@ export const List: FC = (_arg) => { useEffect(() => { (async () => { - if (local.status === "init") { - local.status = "loading"; - local.render(); + if (typeof on_load === "function") { + if (local.status === "init") { + local.status = "loading"; + local.render(); - local.list = await on_load({ params: local.params }); + local.list = await on_load({ params: local.params }); - local.status = "ready"; - local.render(); + local.status = "ready"; + local.render(); + } } })(); }, [on_load]); @@ -61,13 +63,16 @@ export const List: FC = (_arg) => { ) : ( (local.list || []).map((item, idx) => { + const mapped = map_val(item); const val = (...arg: any[]) => { - const value = get(map_val(item), `${arg.join("")}`); + const value = get(mapped, `${arg.join("")}`); return value; }; return (
- {row} + + {row} +
); }) @@ -76,8 +81,8 @@ export const List: FC = (_arg) => { )}
- ); -}; + ); +}; const ListDummy = ({ props, row, PassProp, map_val, mode }: ListProp) => { const item = (...arg: string[]) => { diff --git a/comps/ui/button.tsx b/comps/ui/button.tsx index 29db581..b162a5a 100755 --- a/comps/ui/button.tsx +++ b/comps/ui/button.tsx @@ -56,4 +56,21 @@ const Button = React.forwardRef( ); Button.displayName = "Button"; -export { Button, buttonVariants }; +const FloatButton = React.forwardRef( + ({ variant, className, ...props }, ref) => { + return ( +