update 11 files
This commit is contained in:
parent
98598fa984
commit
2c1acf2007
|
|
@ -3,7 +3,6 @@ import { FieldCheckbox } from "./field/TypeCheckbox";
|
||||||
import { TypeDropdown } from "./field/TypeDropdown";
|
import { TypeDropdown } from "./field/TypeDropdown";
|
||||||
import { TypeInput } from "./field/TypeInput";
|
import { TypeInput } from "./field/TypeInput";
|
||||||
import { TypeUpload } from "./field/TypeUpload";
|
import { TypeUpload } from "./field/TypeUpload";
|
||||||
import { FieldUploadMulti } from "./field/TypeUploadMulti";
|
|
||||||
import { TypeRichText } from "./field/TypeRichText";
|
import { TypeRichText } from "./field/TypeRichText";
|
||||||
import { TypeTag } from "./field/TypeTag";
|
import { TypeTag } from "./field/TypeTag";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
|
|
@ -23,6 +22,7 @@ export const Field: React.FC<any> = ({
|
||||||
hidden_label,
|
hidden_label,
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
|
classField,
|
||||||
style,
|
style,
|
||||||
prefix,
|
prefix,
|
||||||
suffix,
|
suffix,
|
||||||
|
|
@ -98,11 +98,16 @@ export const Field: React.FC<any> = ({
|
||||||
{!hidden_label ? (
|
{!hidden_label ? (
|
||||||
<label
|
<label
|
||||||
className={cx(
|
className={cx(
|
||||||
"block mb-2 text-md font-medium text-gray-900 text-sm",
|
"block mb-2 text-md font-medium text-gray-900 text-sm flex flex-row",
|
||||||
style === "inline" ? "w-[100px]" : ""
|
style === "inline" ? "w-[100px]" : ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
|
{required ? (
|
||||||
|
<span className="flex flex-row px-0.5 text-red-500">*</span>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
@ -137,7 +142,8 @@ export const Field: React.FC<any> = ({
|
||||||
["upload"].includes(type) &&
|
["upload"].includes(type) &&
|
||||||
css`
|
css`
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
`
|
`,
|
||||||
|
classField
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{before && (
|
{before && (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
import { Form } from "./Form";
|
import { Form } from "./Form";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
@ -21,14 +22,15 @@ export const FormBetter: React.FC<any> = ({
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-grow gap-y-3">
|
<div className="flex flex-col flex-grow gap-y-3">
|
||||||
{typeof fm === "object" && typeof onTitle === "function" ? (
|
{typeof fm === "object" && typeof onTitle === "function" ? (
|
||||||
<div className="flex flex-row p-3 items-center bg-white border border-gray-300 rounded-lg">
|
<div className="flex flex-row p-3 items-center rounded-lg">
|
||||||
{onTitle(fm)}
|
{onTitle(fm)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
<div className="w-full flex-grow flex flex-row rounded-lg overflow-hidden">
|
<div className="w-full flex-grow flex flex-row rounded-lg">
|
||||||
<div className="w-full flex flex-row flex-grow bg-white rounded-lg border border-gray-300 relative overflow-y-scroll">
|
<div className="w-full flex flex-row flex-grow bg-white rounded-lg border border-gray-300 relative">
|
||||||
|
<ScrollArea className="flex-grow">
|
||||||
<Form
|
<Form
|
||||||
{...{
|
{...{
|
||||||
children,
|
children,
|
||||||
|
|
@ -39,7 +41,7 @@ export const FormBetter: React.FC<any> = ({
|
||||||
onFooter,
|
onFooter,
|
||||||
showResize,
|
showResize,
|
||||||
mode,
|
mode,
|
||||||
className: cx(className, "absolute top-0 left-0 w-full"),
|
className: cx(className, "top-0 left-0 w-full"),
|
||||||
onInit: (form: any) => {
|
onInit: (form: any) => {
|
||||||
setFM(form);
|
setFM(form);
|
||||||
|
|
||||||
|
|
@ -65,6 +67,7 @@ export const FormBetter: React.FC<any> = ({
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,13 @@ export const ThumbPreview = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilePreview = ({ url }: { url: any }) => {
|
export const FilePreview = ({
|
||||||
|
url,
|
||||||
|
disabled,
|
||||||
|
}: {
|
||||||
|
url: any;
|
||||||
|
disabled?: boolean;
|
||||||
|
}) => {
|
||||||
let ural = url;
|
let ural = url;
|
||||||
if (url instanceof File) {
|
if (url instanceof File) {
|
||||||
ural = `${URL.createObjectURL(url)}.${url.name.split(".").pop()}`;
|
ural = `${URL.createObjectURL(url)}.${url.name.split(".").pop()}`;
|
||||||
|
|
@ -211,7 +217,7 @@ export const FilePreview = ({ url }: { url: any }) => {
|
||||||
{file.extension && (
|
{file.extension && (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"flex rounded items-center px-1 bg-white cursor-pointer flex-grow hover:bg-gray-100 gap-x-1",
|
"flex rounded items-center px-1 cursor-pointer flex-grow hover:bg-gray-100 gap-x-1 justify-between",
|
||||||
"pr-2",
|
"pr-2",
|
||||||
css`
|
css`
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
@ -222,7 +228,8 @@ export const FilePreview = ({ url }: { url: any }) => {
|
||||||
// border-bottom: 1px solid #1c4ed8;
|
// border-bottom: 1px solid #1c4ed8;
|
||||||
// outline: 1px solid #1c4ed8;
|
// outline: 1px solid #1c4ed8;
|
||||||
}
|
}
|
||||||
`
|
`,
|
||||||
|
disabled ? "bg-transparent" : "bg-white"
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let _url: any =
|
let _url: any =
|
||||||
|
|
@ -231,8 +238,10 @@ export const FilePreview = ({ url }: { url: any }) => {
|
||||||
window.open(_url, "_blank");
|
window.open(_url, "_blank");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className="flex flex-row gap-x-1 items-center">
|
||||||
<div className="h-[30px] flex flex-row items-center">{content}</div>
|
<div className="h-[30px] flex flex-row items-center">{content}</div>
|
||||||
<div className="text-xs filename">{file?.name}</div>
|
<div className="text-xs filename">{file?.name}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<ExternalLink size="12px" />
|
<ExternalLink size="12px" />
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Textarea } from "../../ui/text-area";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import tinycolor from "tinycolor2";
|
import tinycolor from "tinycolor2";
|
||||||
import { FieldColorPicker } from "../../ui/FieldColorPopover";
|
import { FieldColorPicker } from "../../ui/FieldColorPopover";
|
||||||
import { FaRegStar, FaStar } from "react-icons/fa6";
|
import { FaRegEye, FaRegEyeSlash, FaRegStar, FaStar } from "react-icons/fa6";
|
||||||
import { Rating } from "../../ui/ratings";
|
import { Rating } from "../../ui/ratings";
|
||||||
import { getNumber } from "@/lib/utils/getNumber";
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
import MaskedInput from "../../ui/MaskedInput";
|
import MaskedInput from "../../ui/MaskedInput";
|
||||||
|
|
@ -34,6 +34,7 @@ export const TypeInput: React.FC<any> = ({
|
||||||
const input = useLocal({
|
const input = useLocal({
|
||||||
value: 0 as any,
|
value: 0 as any,
|
||||||
ref: null as any,
|
ref: null as any,
|
||||||
|
show_pass: false as boolean,
|
||||||
open: false,
|
open: false,
|
||||||
});
|
});
|
||||||
const meta = useLocal({
|
const meta = useLocal({
|
||||||
|
|
@ -319,6 +320,10 @@ export const TypeInput: React.FC<any> = ({
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
let type_field = type;
|
||||||
|
if (input.show_pass) {
|
||||||
|
type_field = "text";
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -341,7 +346,7 @@ export const TypeInput: React.FC<any> = ({
|
||||||
required={required}
|
required={required}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
value={value}
|
value={value}
|
||||||
type={!type ? "text" : type}
|
type={!type ? "text" : type_field}
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
fm.data[name] = ev.currentTarget.value;
|
fm.data[name] = ev.currentTarget.value;
|
||||||
fm.render();
|
fm.render();
|
||||||
|
|
@ -350,6 +355,24 @@ export const TypeInput: React.FC<any> = ({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{type === "password" && (
|
||||||
|
<div
|
||||||
|
className=" right-0 h-full flex items-center cursor-pointer px-2"
|
||||||
|
onClick={() => {
|
||||||
|
input.show_pass = !input.show_pass;
|
||||||
|
input.render();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="">
|
||||||
|
{input.show_pass ? (
|
||||||
|
<FaRegEyeSlash className="h-4" />
|
||||||
|
) : (
|
||||||
|
<FaRegEye className="h-4" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import TableCell from "@tiptap/extension-table-cell";
|
||||||
import TableHeader from "@tiptap/extension-table-header";
|
import TableHeader from "@tiptap/extension-table-header";
|
||||||
import TableRow from "@tiptap/extension-table-row";
|
import TableRow from "@tiptap/extension-table-row";
|
||||||
import { ButtonRichText } from "../../ui/button-rich-text";
|
import { ButtonRichText } from "../../ui/button-rich-text";
|
||||||
import { InitEditor } from "./AfterEditor";
|
|
||||||
|
|
||||||
export const TypeRichText: React.FC<any> = ({
|
export const TypeRichText: React.FC<any> = ({
|
||||||
name,
|
name,
|
||||||
|
|
@ -822,7 +821,7 @@ export const TypeRichText: React.FC<any> = ({
|
||||||
onChange(fm.data[name]);
|
onChange(fm.data[name]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
content={input.value}
|
content={fm.data?.[name]}
|
||||||
editable={!disabled}
|
editable={!disabled}
|
||||||
></EditorProvider>
|
></EditorProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,10 @@ export const FieldUploadSingle: FC<{
|
||||||
</div>
|
</div>
|
||||||
) : input.fase === "preview" ? (
|
) : input.fase === "preview" ? (
|
||||||
<div className="flex flex-row gap-x-1 justify-between flex-1 p-1">
|
<div className="flex flex-row gap-x-1 justify-between flex-1 p-1">
|
||||||
<FilePreview url={input.isLocal ? input.preview : value || ""} />
|
<FilePreview
|
||||||
|
url={input.isLocal ? input.preview : value || ""}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
{!disabled ? (
|
{!disabled ? (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { detectCase } from "@/lib/utils/detectCase";
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
import { get_user } from "@/lib/utils/get_user";
|
import { get_user } from "@/lib/utils/get_user";
|
||||||
import { siteurl } from "@/lib/utils/siteurl";
|
import { siteurl } from "@/lib/utils/siteurl";
|
||||||
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
interface TreeMenuItem {
|
interface TreeMenuItem {
|
||||||
title: string;
|
title: string;
|
||||||
href?: string;
|
href?: string;
|
||||||
|
|
@ -320,7 +321,7 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
<Sidebar
|
<Sidebar
|
||||||
aria-label="Sidebar with multi-level dropdown example"
|
aria-label="Sidebar with multi-level dropdown example"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"relative pt-0 sidebar rounded-none",
|
"relative pt-0 rounded-none",
|
||||||
mini ? "w-20" : "",
|
mini ? "w-20" : "",
|
||||||
css`
|
css`
|
||||||
> div {
|
> div {
|
||||||
|
|
@ -332,8 +333,9 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<ScrollArea className="w-full h-full">
|
||||||
<div className="w-full h-full relative ">
|
<div className="w-full h-full relative ">
|
||||||
<div className="flex h-full flex-col justify-between w-full absolute top-0 left-0">
|
<div className="flex h-full flex-col justify-between w-full">
|
||||||
<Sidebar.Items>
|
<Sidebar.Items>
|
||||||
<Sidebar.ItemGroup
|
<Sidebar.ItemGroup
|
||||||
className={cx(
|
className={cx(
|
||||||
|
|
@ -346,6 +348,7 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
</Sidebar.Items>
|
</Sidebar.Items>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,775 @@
|
||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import { Avatar, Sidebar } from "flowbite-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { css } from "@emotion/css";
|
||||||
|
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
|
||||||
|
import { SidebarLinkBetter } from "../ui/link-better";
|
||||||
|
import { detectCase } from "@/lib/utils/detectCase";
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import { get_user } from "@/lib/utils/get_user";
|
||||||
|
import { siteurl } from "@/lib/utils/siteurl";
|
||||||
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
|
import { LuChevronsLeftRight } from "react-icons/lu";
|
||||||
|
import { Popover } from "../Popover/Popover";
|
||||||
|
import { HiEye } from "react-icons/hi";
|
||||||
|
import { MdOutlineMoreVert } from "react-icons/md";
|
||||||
|
import api from "@/lib/utils/axios";
|
||||||
|
import { PiUserSwitch } from "react-icons/pi";
|
||||||
|
import { IoIosLogOut } from "react-icons/io";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "../ui/tooltip";
|
||||||
|
|
||||||
|
interface TreeMenuItem {
|
||||||
|
title: string;
|
||||||
|
href?: string;
|
||||||
|
children?: TreeMenuItem[];
|
||||||
|
icon?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TreeMenuProps {
|
||||||
|
data: TreeMenuItem[];
|
||||||
|
minimaze: () => void;
|
||||||
|
mini: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarBetterTree: React.FC<TreeMenuProps> = ({
|
||||||
|
data,
|
||||||
|
minimaze,
|
||||||
|
mini,
|
||||||
|
}) => {
|
||||||
|
const [currentPage, setCurrentPage] = useState("");
|
||||||
|
const [notification, setNotification] = useState(false as boolean);
|
||||||
|
const [profile, setProfile] = useState(false as boolean);
|
||||||
|
const local = useLocal({
|
||||||
|
data: data,
|
||||||
|
ready: false as boolean,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof location === "object") {
|
||||||
|
const newPage = window.location.pathname;
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
}
|
||||||
|
const run = async () => {
|
||||||
|
local.ready = false;
|
||||||
|
local.render();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
local.ready = true;
|
||||||
|
local.render();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
if (typeof window === "object") {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isChildActive = (items: TreeMenuItem[]): boolean => {
|
||||||
|
return items.some((item) => {
|
||||||
|
if (item.href && currentPage.startsWith(item.href)) return true;
|
||||||
|
if (item.children) return isChildActive(item.children); // Rekursif
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const renderTree = (items: TreeMenuItem[], depth: number = 0) => {
|
||||||
|
return items.map((item, index) => {
|
||||||
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
let isActive = item.href && detectCase(currentPage, item.href);
|
||||||
|
let isParentActive = hasChildren && isChildActive(item.children!);
|
||||||
|
const [isOpen, setIsOpen] = useState(isParentActive);
|
||||||
|
useEffect(() => {
|
||||||
|
if (isParentActive) {
|
||||||
|
setIsOpen(true);
|
||||||
|
}
|
||||||
|
}, [isParentActive]);
|
||||||
|
const itemStyle = {
|
||||||
|
paddingLeft:
|
||||||
|
mini && !depth
|
||||||
|
? "10px"
|
||||||
|
: !hasChildren && !depth
|
||||||
|
? "13px"
|
||||||
|
: !mini
|
||||||
|
? `${depth * 16}px`
|
||||||
|
: "0px",
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<React.Fragment key={item.href || item.title || index}>
|
||||||
|
{hasChildren ? (
|
||||||
|
<li className="relative ">
|
||||||
|
<div className="w-full flex flex-row items-center justify-center">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
mini ? "flex flex-row" : "flex flex-row flex-grow"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{mini && !depth ? (
|
||||||
|
<>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"transition-all font-bold text-sidebar-label relative flex-row flex items-center cursor-pointer items-center text-base flex flex-row ",
|
||||||
|
isActive
|
||||||
|
? " text-base"
|
||||||
|
: " hover:bg-card-layer hover:shadow-md hover:text-primary",
|
||||||
|
mini
|
||||||
|
? "transition-all duration-200 justify-center ml-0 rounded-lg"
|
||||||
|
: "py-2.5 px-4 rounded-md flex-grow mx-2 pr-0",
|
||||||
|
!depth && !hasChildren ? "px-2" : "",
|
||||||
|
css`
|
||||||
|
& > span {
|
||||||
|
white-space: wrap !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
mini
|
||||||
|
? isActive
|
||||||
|
? !depth
|
||||||
|
? "bg-linear-sidebar-active text-white font-normal p-1 shadow-md"
|
||||||
|
: " bg-layer text-primary font-bold p-1 "
|
||||||
|
: "text-primary bg-transparent p-1 hover:bg-card-layer hover:shadow-md w-[50px]"
|
||||||
|
: isActive
|
||||||
|
? !depth
|
||||||
|
? " bg-linear-sidebar-active font-bold text-white shadow-md "
|
||||||
|
: " bg-layer text-primary font-bold "
|
||||||
|
: "text-white"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (mini) {
|
||||||
|
minimaze();
|
||||||
|
}
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
}}
|
||||||
|
style={mini ? {} : itemStyle}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center flex-grow",
|
||||||
|
mini
|
||||||
|
? " justify-center rounded-md "
|
||||||
|
: " px-3",
|
||||||
|
mini
|
||||||
|
? isParentActive
|
||||||
|
? "bg-linear-sidebar-active text-white font-bold "
|
||||||
|
: "text-primary"
|
||||||
|
: isActive
|
||||||
|
? "font-bold "
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!depth ? (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
" w-8 h-8 text-center flex flex-row items-center justify-center",
|
||||||
|
!mini ? "mr-1 p-2 " : " text-lg ",
|
||||||
|
mini
|
||||||
|
? css`
|
||||||
|
background: transparent !important;
|
||||||
|
`
|
||||||
|
: ``
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!mini ? (
|
||||||
|
<>
|
||||||
|
<div className="pl-2 flex-grow text-xs line-clamp-2">
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<div className="text-md px-1">
|
||||||
|
{isOpen ? (
|
||||||
|
<FaChevronUp />
|
||||||
|
) : (
|
||||||
|
<FaChevronDown />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent
|
||||||
|
className="bg-linear-sidebar-active text-white border border-primary shadow-md transition-all "
|
||||||
|
side="right"
|
||||||
|
>
|
||||||
|
<p>{item.title}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"transition-all font-bold text-sidebar-label relative flex-row flex items-center cursor-pointer items-center text-base flex flex-row ",
|
||||||
|
isActive
|
||||||
|
? " text-base"
|
||||||
|
: " hover:bg-card-layer hover:shadow-md hover:text-primary",
|
||||||
|
mini
|
||||||
|
? "transition-all duration-200 justify-center ml-0 rounded-lg"
|
||||||
|
: "py-2.5 px-4 rounded-md flex-grow mx-2 pr-0",
|
||||||
|
!depth && !hasChildren ? "px-2" : "",
|
||||||
|
css`
|
||||||
|
& > span {
|
||||||
|
white-space: wrap !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
mini
|
||||||
|
? isActive
|
||||||
|
? !depth
|
||||||
|
? "bg-linear-sidebar-active text-white font-normal p-1 shadow-md"
|
||||||
|
: " bg-layer text-primary font-bold p-1 "
|
||||||
|
: "text-primary bg-transparent p-1 hover:bg-card-layer hover:shadow-md w-[50px]"
|
||||||
|
: isActive
|
||||||
|
? !depth
|
||||||
|
? " bg-linear-sidebar-active font-bold text-white shadow-md "
|
||||||
|
: " bg-layer text-primary font-bold "
|
||||||
|
: "text-sidebar-label"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (mini) {
|
||||||
|
minimaze();
|
||||||
|
}
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
}}
|
||||||
|
style={mini ? {} : itemStyle}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center flex-grow",
|
||||||
|
mini ? " justify-center rounded-md " : " px-3",
|
||||||
|
mini
|
||||||
|
? isParentActive
|
||||||
|
? "bg-linear-sidebar-active text-white font-bold "
|
||||||
|
: "text-primary"
|
||||||
|
: isActive
|
||||||
|
? "font-bold "
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!depth ? (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
" w-8 h-8 text-center flex flex-row items-center justify-center",
|
||||||
|
!mini ? "mr-1 p-2 " : " text-lg ",
|
||||||
|
mini
|
||||||
|
? css`
|
||||||
|
background: transparent !important;
|
||||||
|
`
|
||||||
|
: ``
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!mini ? (
|
||||||
|
<>
|
||||||
|
<div className="pl-2 flex-grow text-xs line-clamp-2">
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<div className="text-md px-1">
|
||||||
|
{isOpen ? <FaChevronUp /> : <FaChevronDown />}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Sidebar.ItemGroup
|
||||||
|
className={classNames(
|
||||||
|
"border-none mt-0",
|
||||||
|
isOpen ? "" : "hidden",
|
||||||
|
mini ? "hidden" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{renderTree(item.children!, depth + 1)}
|
||||||
|
</Sidebar.ItemGroup>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li className="relative">
|
||||||
|
<div className="w-full flex flex-row items-center justify-center">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
mini ? "flex flex-row" : "flex flex-row w-full"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{mini ? (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div>
|
||||||
|
<SidebarLinkBetter
|
||||||
|
href={item.href}
|
||||||
|
onClick={() => {
|
||||||
|
if (item?.href) setCurrentPage(item.href);
|
||||||
|
}}
|
||||||
|
className={classNames(
|
||||||
|
"transition-all font-bold relative flex-row flex items-center cursor-pointer items-center text-base flex flex-row ",
|
||||||
|
isActive
|
||||||
|
? " text-base"
|
||||||
|
: "hover:bg-card-layer hover:shadow-md hover:text-primary",
|
||||||
|
mini
|
||||||
|
? "transition-all duration-200 justify-center ml-0 rounded-lg"
|
||||||
|
: "py-2.5 px-4 rounded-md flex-grow mx-2",
|
||||||
|
!depth && !hasChildren ? "px-2" : "",
|
||||||
|
css`
|
||||||
|
& > span {
|
||||||
|
white-space: wrap !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
mini
|
||||||
|
? isActive
|
||||||
|
? !depth
|
||||||
|
? "bg-linear-sidebar-active text-white font-normal p-1 shadow-md"
|
||||||
|
: " bg-layer text-primary font-bold p-1 "
|
||||||
|
: "text-primary bg-transparent p-1 hover:bg-card-layer hover:shadow-md"
|
||||||
|
: isActive
|
||||||
|
? !depth
|
||||||
|
? " bg-linear-sidebar-active font-bold text-white shadow-md"
|
||||||
|
: " bg-linear-sidebar-active text-white font-bold "
|
||||||
|
: "text-sidebar-label"
|
||||||
|
)}
|
||||||
|
style={mini ? {} : itemStyle} // Terapkan gaya berdasarkan depth
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
{!depth ? (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
" text-dark-700 w-8 h-8 rounded-lg text-center flex flex-row items-center justify-center",
|
||||||
|
!mini ? "mr-1 p-2" : " text-lg"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{!mini ? (
|
||||||
|
<>
|
||||||
|
<div className="pl-2 text-xs line-clamp-1">
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SidebarLinkBetter>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent
|
||||||
|
className="bg-linear-sidebar-active text-white border border-primary shadow-md transition-all "
|
||||||
|
side="right"
|
||||||
|
>
|
||||||
|
<p>{item.title}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
) : (
|
||||||
|
<SidebarLinkBetter
|
||||||
|
href={item.href}
|
||||||
|
onClick={() => {
|
||||||
|
if (item?.href) setCurrentPage(item.href);
|
||||||
|
}}
|
||||||
|
className={classNames(
|
||||||
|
"transition-all font-bold relative flex-row flex items-center cursor-pointer items-center text-base flex flex-row ",
|
||||||
|
isActive
|
||||||
|
? " text-base"
|
||||||
|
: "hover:bg-card-layer hover:shadow-md hover:text-primary",
|
||||||
|
mini
|
||||||
|
? "transition-all duration-200 justify-center ml-0 rounded-lg"
|
||||||
|
: "py-2.5 px-4 rounded-md flex-grow mx-2",
|
||||||
|
!depth && !hasChildren ? "px-2" : "",
|
||||||
|
css`
|
||||||
|
& > span {
|
||||||
|
white-space: wrap !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
mini
|
||||||
|
? isActive
|
||||||
|
? !depth
|
||||||
|
? "bg-linear-sidebar-active text-white font-normal p-1 shadow-md"
|
||||||
|
: " bg-layer text-primary font-bold p-1 "
|
||||||
|
: "text-primary bg-transparent p-1 hover:bg-card-layer hover:shadow-md"
|
||||||
|
: isActive
|
||||||
|
? !depth
|
||||||
|
? " bg-linear-sidebar-active font-bold text-white shadow-md"
|
||||||
|
: " bg-linear-sidebar-active text-white font-bold "
|
||||||
|
: "text-sidebar-label"
|
||||||
|
)}
|
||||||
|
style={mini ? {} : itemStyle} // Terapkan gaya berdasarkan depth
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
{!depth ? (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
" text-dark-700 w-8 h-8 rounded-lg text-center flex flex-row items-center justify-center",
|
||||||
|
!mini ? "mr-1 p-2" : " text-lg"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{!mini ? (
|
||||||
|
<>
|
||||||
|
<div className="pl-2 text-xs line-clamp-1">
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SidebarLinkBetter>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col flex-grow bg-white relative rounded-b-2xl">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"bg-linear-sidebar-active text-white p-1 absolute top-0 right-[-13px] cursor-pointer rounded-lg shadow-md",
|
||||||
|
css`
|
||||||
|
z-index: 1;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
minimaze();
|
||||||
|
localStorage.setItem("mini", !mini ? "true" : "false");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LuChevronsLeftRight />
|
||||||
|
</div>
|
||||||
|
<div className={classNames("flex flex-grow lg:!block ", {})}>
|
||||||
|
<Sidebar
|
||||||
|
aria-label="Sidebar with multi-level dropdown example"
|
||||||
|
className={classNames(
|
||||||
|
"relative pt-0 rounded-none",
|
||||||
|
mini ? "w-20" : "",
|
||||||
|
css`
|
||||||
|
> div {
|
||||||
|
border-radius: 0px;
|
||||||
|
background: transparent;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ScrollArea className="w-full h-full">
|
||||||
|
<div className="w-full h-full relative ">
|
||||||
|
<div className="flex h-full flex-col w-full">
|
||||||
|
<Sidebar.Items>
|
||||||
|
<Sidebar.ItemGroup
|
||||||
|
className={cx(
|
||||||
|
"border-none mt-0 pt-4",
|
||||||
|
mini ? "flex flex-col gap-y-2" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{renderTree(data)}
|
||||||
|
</Sidebar.ItemGroup>
|
||||||
|
</Sidebar.Items>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</Sidebar>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-y-4">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-col flex-grow gap-y-2",
|
||||||
|
mini ? "justify-center" : "px-4"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-center flex-grow">
|
||||||
|
<Popover
|
||||||
|
classNameTrigger={cx("flex flex-row justify-center flex-grow")}
|
||||||
|
popoverClassName={cx(
|
||||||
|
"max-w-[24rem] bg-white shadow-lg rounded-lg"
|
||||||
|
)}
|
||||||
|
placement={"right-start"}
|
||||||
|
arrow={false}
|
||||||
|
className="rounded-md w-full "
|
||||||
|
onOpenChange={(open: any) => {
|
||||||
|
setNotification(open);
|
||||||
|
}}
|
||||||
|
open={notification}
|
||||||
|
content={
|
||||||
|
<div className="max-w-[24rem]">
|
||||||
|
<div className="block rounded-t-xl bg-gray-50 py-2 px-4 text-center text-base font-medium text-gray-700">
|
||||||
|
Notifications
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="flex border-y py-3 px-4 hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<div className="w-full pl-3">
|
||||||
|
<div className="mb-1.5 text-md font-normal text-gray-500 ">
|
||||||
|
New message from
|
||||||
|
<span className="font-semibold text-gray-900 ">
|
||||||
|
Bonnie Green
|
||||||
|
</span>
|
||||||
|
: "Hey, what's up? All set for the presentation?"
|
||||||
|
</div>
|
||||||
|
<div className="text-md font-medium text-primary-700 ">
|
||||||
|
a few moments ago
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="flex border-y py-3 px-4 hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<div className="w-full pl-3">
|
||||||
|
<div className="mb-1.5 text-md font-normal text-gray-500 ">
|
||||||
|
New message from
|
||||||
|
<span className="font-semibold text-gray-900 ">
|
||||||
|
Bonnie Green
|
||||||
|
</span>
|
||||||
|
: "Hey, what's up? All set for the presentation?"
|
||||||
|
</div>
|
||||||
|
<div className="text-md font-medium text-primary-700 ">
|
||||||
|
a few moments ago
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="flex border-y py-3 px-4 hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<div className="w-full pl-3">
|
||||||
|
<div className="mb-1.5 text-md font-normal text-gray-500 ">
|
||||||
|
New message from
|
||||||
|
<span className="font-semibold text-gray-900 ">
|
||||||
|
Bonnie Green
|
||||||
|
</span>
|
||||||
|
: "Hey, what's up? All set for the presentation?"
|
||||||
|
</div>
|
||||||
|
<div className="text-md font-medium text-primary-700 ">
|
||||||
|
a few moments ago
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="flex border-y py-3 px-4 hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<div className="w-full pl-3">
|
||||||
|
<div className="mb-1.5 text-md font-normal text-gray-500 ">
|
||||||
|
New message from
|
||||||
|
<span className="font-semibold text-gray-900 ">
|
||||||
|
Bonnie Green
|
||||||
|
</span>
|
||||||
|
: "Hey, what's up? All set for the presentation?"
|
||||||
|
</div>
|
||||||
|
<div className="text-md font-medium text-primary-700 ">
|
||||||
|
a few moments ago
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block rounded-b-xl bg-gray-50 py-2 text-center text-base font-normal text-gray-900 hover:bg-gray-100 dark:bg-gray-700 dark:hover:underline"
|
||||||
|
>
|
||||||
|
<div className="inline-flex items-center gap-x-2">
|
||||||
|
<HiEye className="h-6 w-6" />
|
||||||
|
<span>View all</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center gap-x-2 items-center text-sm rounded-md font-bold",
|
||||||
|
mini
|
||||||
|
? "justify-center text-primary w-50 p-1 px-2"
|
||||||
|
: "text-sidebar-label w-full p-2",
|
||||||
|
false ? "" : "hover:bg-card-layer hover:shadow-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
" w-8 h-8 text-center flex flex-row items-center justify-center ",
|
||||||
|
!mini ? "mr-1 p-2 " : " text-lg ",
|
||||||
|
mini
|
||||||
|
? css`
|
||||||
|
background: transparent !important;
|
||||||
|
`
|
||||||
|
: ``
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="none" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M6 19v-9a6 6 0 0 1 6-6v0a6 6 0 0 1 6 6v9M6 19h12M6 19H4m14 0h2m-9 3h2"
|
||||||
|
></path>
|
||||||
|
<circle cx={12} cy={3} r={1}></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{!mini && "Notifications"}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row w-full p-2 items-center gap-x-2 items-center text-white text-sm hidden",
|
||||||
|
mini ? "justify-center" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="currentColor">
|
||||||
|
<path d="M6.5 3.75c-.526 0-1.25.63-1.25 1.821V18.43c0 1.192.724 1.821 1.25 1.821h6.996a.75.75 0 1 1 0 1.5H6.5c-1.683 0-2.75-1.673-2.75-3.321V5.57c0-1.648 1.067-3.321 2.75-3.321h7a.75.75 0 0 1 0 1.5z"></path>
|
||||||
|
<path d="M16.53 7.97a.75.75 0 0 0-1.06 0v3.276H9.5a.75.75 0 0 0 0 1.5h5.97v3.284a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0 .22-.532v-.002a.75.75 0 0 0-.269-.575z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
{!mini && "Sign Out"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row w-full p-4 pt-0">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row justify-center flex-grow rounded-lg",
|
||||||
|
mini ? "py-4" : "p-4 bg-primary "
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Avatar alt="" img={siteurl("/dog.jpg")} rounded size="sm" />
|
||||||
|
{!mini && (
|
||||||
|
<div className="flex flex-row items-center flex-grow font-bold text-white">
|
||||||
|
<div className=" px-2 h-full flex flex-col text-xs flex-grow">
|
||||||
|
<div>
|
||||||
|
{get_user("employee.name")
|
||||||
|
? get_user("employee.name")
|
||||||
|
: "-"}
|
||||||
|
</div>
|
||||||
|
<div className="font-normal">
|
||||||
|
{get_user("email") ? get_user("email") : "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
classNameTrigger={cx("flex flex-row justify-center")}
|
||||||
|
popoverClassName={cx(
|
||||||
|
"max-w-[24rem] bg-white shadow-lg rounded-lg"
|
||||||
|
)}
|
||||||
|
placement={"right-start"}
|
||||||
|
arrow={false}
|
||||||
|
className="rounded-md w-full "
|
||||||
|
onOpenChange={(open: any) => {
|
||||||
|
setProfile(open);
|
||||||
|
}}
|
||||||
|
open={profile}
|
||||||
|
content={
|
||||||
|
<div className="max-w-[24rem] rounded-xl overflow-hidden">
|
||||||
|
<div className="block bg-gray-50 py-2 px-4 text-sm font-medium text-gray-700">
|
||||||
|
Profile
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div
|
||||||
|
onClick={async () => {
|
||||||
|
await api.delete(
|
||||||
|
process.env.NEXT_PUBLIC_BASE_URL +
|
||||||
|
"/api/destroy-cookies"
|
||||||
|
);
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
if (typeof window === "object")
|
||||||
|
navigate(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_PORTAL}/logout`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="cursor-pointer px-4 py-2 flex border-y hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="text-sm items-center font-normal text-primary-700 flex flex-row gap-x-2">
|
||||||
|
<PiUserSwitch />
|
||||||
|
Switch Role
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={async () => {
|
||||||
|
await api.delete(
|
||||||
|
process.env.NEXT_PUBLIC_BASE_URL +
|
||||||
|
"/api/destroy-cookies"
|
||||||
|
);
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
if (typeof window === "object")
|
||||||
|
navigate(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_PORTAL}/logout`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="cursor-pointer px-4 py-2 flex hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="text-sm items-center font-normal text-primary-700 flex flex-row gap-x-2">
|
||||||
|
<IoIosLogOut />
|
||||||
|
Sign Out
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="text-white cursor-pointer h-full flex flex-row items-center">
|
||||||
|
<MdOutlineMoreVert />
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidebarBetterTree;
|
||||||
|
|
@ -1,33 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
import {
|
import React, { useEffect, useState } from "react";
|
||||||
ColumnDef,
|
import { Table } from "flowbite-react";
|
||||||
ColumnResizeDirection,
|
import { HiChevronLeft, HiChevronRight } from "react-icons/hi";
|
||||||
ColumnResizeMode,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
SortingState,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
|
||||||
import { Button, Label, Table } from "flowbite-react";
|
|
||||||
import { HiChevronLeft, HiChevronRight, HiPlus } from "react-icons/hi";
|
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
import { debouncedHandler } from "@/lib/utils/debounceHandler";
|
|
||||||
import { FaChevronUp } from "react-icons/fa6";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { init_column } from "./lib/column";
|
import { init_column } from "./lib/column";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Loader2, Sticker } from "lucide-react";
|
import { Loader2, Sticker } from "lucide-react";
|
||||||
import { InputSearch } from "../ui/input-search";
|
|
||||||
import { FaChevronDown } from "react-icons/fa";
|
|
||||||
import get from "lodash.get";
|
|
||||||
import { Checkbox } from "../ui/checkbox";
|
|
||||||
import { getNumber } from "@/lib/utils/getNumber";
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
import { formatMoney } from "../form/field/TypeInput";
|
import { formatMoney } from "../form/field/TypeInput";
|
||||||
import { cloneFM } from "@/lib/utils/cloneFm";
|
|
||||||
import { ResizableBox } from "react-resizable";
|
|
||||||
export const TableEditBetter: React.FC<any> = ({
|
export const TableEditBetter: React.FC<any> = ({
|
||||||
name,
|
name,
|
||||||
column,
|
column,
|
||||||
|
|
@ -250,7 +230,14 @@ export const TableEditBetter: React.FC<any> = ({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!disabledHeadTable ? (
|
{!disabledHeadTable ? (
|
||||||
<thead className="rounded-md overflow-hidden text-md bg-second group/head text-md uppercase text-gray-700 sticky top-0">
|
<thead
|
||||||
|
className={cx(
|
||||||
|
"rounded-md overflow-hidden text-md bg-primary group/head text-md uppercase text-white sticky top-0",
|
||||||
|
css`
|
||||||
|
z-index: 1;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
<tr className={"table-header-tbl"}>
|
<tr className={"table-header-tbl"}>
|
||||||
{columns.map((col, idx) => {
|
{columns.map((col, idx) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -265,7 +252,7 @@ export const TableEditBetter: React.FC<any> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center h-full flex-grow p-2">
|
<div className="flex items-center h-full flex-grow p-2">
|
||||||
<span>{col?.name}</span>
|
<span>{col?.header()}</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
|
|
@ -277,9 +264,25 @@ export const TableEditBetter: React.FC<any> = ({
|
||||||
)}
|
)}
|
||||||
<tbody>
|
<tbody>
|
||||||
{local.data.map((row: any, index: any) => {
|
{local.data.map((row: any, index: any) => {
|
||||||
const fm_row = cloneFM(fm, row);
|
const fm_row = {
|
||||||
|
...fm,
|
||||||
|
|
||||||
|
data: row,
|
||||||
|
render: () => {
|
||||||
|
local.render();
|
||||||
|
fm.data[name] = local.data;
|
||||||
|
fm.render();
|
||||||
|
},
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<tr key={`row_${name}_${index}`}>
|
<tr
|
||||||
|
key={`row_${name}_${index}`}
|
||||||
|
className={cx(css`
|
||||||
|
td {
|
||||||
|
vertical-align: ${align};
|
||||||
|
}
|
||||||
|
`)}
|
||||||
|
>
|
||||||
{columns.map((col, idx) => {
|
{columns.map((col, idx) => {
|
||||||
const param = {
|
const param = {
|
||||||
row: row,
|
row: row,
|
||||||
|
|
@ -300,7 +303,7 @@ export const TableEditBetter: React.FC<any> = ({
|
||||||
key={`row_${name}_${index}_${col?.accessorKey}_${idx}`}
|
key={`row_${name}_${index}_${col?.accessorKey}_${idx}`}
|
||||||
className={"table-header-tbl capitalize"}
|
className={"table-header-tbl capitalize"}
|
||||||
>
|
>
|
||||||
{renderData}
|
<div className="p-1">{renderData}</div>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -15,22 +15,21 @@ import { Button, Label, Table } from "flowbite-react";
|
||||||
import { HiChevronLeft, HiChevronRight, HiPlus } from "react-icons/hi";
|
import { HiChevronLeft, HiChevronRight, HiPlus } from "react-icons/hi";
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
import { debouncedHandler } from "@/lib/utils/debounceHandler";
|
import { debouncedHandler } from "@/lib/utils/debounceHandler";
|
||||||
import { FaChevronUp } from "react-icons/fa6";
|
import { FaSort, FaSortDown, FaSortUp } from "react-icons/fa6";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { init_column } from "./lib/column";
|
import { init_column } from "./lib/column";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Loader2, Sticker } from "lucide-react";
|
import { Loader2, Sticker } from "lucide-react";
|
||||||
import { InputSearch } from "../ui/input-search";
|
import { InputSearch } from "../ui/input-search";
|
||||||
import { FaChevronDown } from "react-icons/fa";
|
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Checkbox } from "../ui/checkbox";
|
import { Checkbox } from "../ui/checkbox";
|
||||||
import { getNumber } from "@/lib/utils/getNumber";
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
import { formatMoney } from "../form/field/TypeInput";
|
import { formatMoney } from "../form/field/TypeInput";
|
||||||
import { cloneFM } from "@/lib/utils/cloneFm";
|
|
||||||
|
|
||||||
export const TableList: React.FC<any> = ({
|
export const TableList: React.FC<any> = ({
|
||||||
name,
|
name,
|
||||||
column,
|
column,
|
||||||
|
style = "UI",
|
||||||
align = "center",
|
align = "center",
|
||||||
onLoad,
|
onLoad,
|
||||||
take = 20,
|
take = 20,
|
||||||
|
|
@ -149,7 +148,18 @@ export const TableList: React.FC<any> = ({
|
||||||
});
|
});
|
||||||
const cloneListFM = (data: any[]) => {
|
const cloneListFM = (data: any[]) => {
|
||||||
if (mode === "form") {
|
if (mode === "form") {
|
||||||
local.dataForm = data.map((e: any) => cloneFM(fm, e));
|
local.dataForm = data.map((e: any) => {
|
||||||
|
return {
|
||||||
|
...fm,
|
||||||
|
data: e,
|
||||||
|
render: () => {
|
||||||
|
local.render();
|
||||||
|
fm.data[name] = local.data;
|
||||||
|
fm.render();
|
||||||
|
console.log(fm.data[name]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
local.render();
|
local.render();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -182,13 +192,18 @@ export const TableList: React.FC<any> = ({
|
||||||
|
|
||||||
local.render();
|
local.render();
|
||||||
}
|
}
|
||||||
|
if (mode === "form") {
|
||||||
|
local.data = fm.data?.[name] || [];
|
||||||
|
cloneListFM(fm.data?.[name] || []);
|
||||||
|
local.render();
|
||||||
|
setData(fm.data?.[name] || []);
|
||||||
|
} else {
|
||||||
if (Array.isArray(onLoad)) {
|
if (Array.isArray(onLoad)) {
|
||||||
local.data = onLoad;
|
local.data = onLoad;
|
||||||
cloneListFM(onLoad);
|
cloneListFM(onLoad);
|
||||||
local.render();
|
local.render();
|
||||||
setData(onLoad);
|
setData(onLoad);
|
||||||
} else {
|
} else if (typeof onLoad === "function") {
|
||||||
const res: any = await onLoad({
|
const res: any = await onLoad({
|
||||||
search: local.search,
|
search: local.search,
|
||||||
sort: local.sort,
|
sort: local.sort,
|
||||||
|
|
@ -199,10 +214,16 @@ export const TableList: React.FC<any> = ({
|
||||||
cloneListFM(res);
|
cloneListFM(res);
|
||||||
local.render();
|
local.render();
|
||||||
setData(res);
|
setData(res);
|
||||||
|
} else {
|
||||||
|
local.data = onLoad;
|
||||||
|
cloneListFM(onLoad);
|
||||||
|
local.render();
|
||||||
|
setData(onLoad);
|
||||||
|
}
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if (typeof onInit === "function") {
|
if (typeof onInit === "function") {
|
||||||
onInit(local);
|
onInit(local);
|
||||||
|
|
@ -337,7 +358,7 @@ export const TableList: React.FC<any> = ({
|
||||||
<>
|
<>
|
||||||
<div className="tbl-wrapper flex flex-grow flex-col">
|
<div className="tbl-wrapper flex flex-grow flex-col">
|
||||||
{!disabledHeader ? (
|
{!disabledHeader ? (
|
||||||
<div className="head-tbl-list block items-start justify-between bg-white px-0 py-4 sm:flex">
|
<div className="head-tbl-list block items-start justify-between px-4 py-4 sm:flex">
|
||||||
<div className="flex flex-row items-end">
|
<div className="flex flex-row items-end">
|
||||||
<div className="sm:flex flex flex-col space-y-2">
|
<div className="sm:flex flex flex-col space-y-2">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
|
|
@ -399,28 +420,28 @@ export const TableList: React.FC<any> = ({
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Table
|
<Table
|
||||||
className={cx(
|
className={cx(
|
||||||
"min-w-full divide-y divide-gray-200 ",
|
"min-w-full divide-y divide-gray-200 table-bg",
|
||||||
css`
|
css`
|
||||||
thead th:first-child {
|
// thead th:first-child {
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
border-top-left-radius: 10px; /* Sudut kiri atas */
|
// border-top-left-radius: 10px; /* Sudut kiri atas */
|
||||||
border-bottom-left-radius: 10px;
|
// border-bottom-left-radius: 10px;
|
||||||
}
|
// }
|
||||||
thead th:last-child {
|
// thead th:last-child {
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
border-top-right-radius: 10px; /* Sudut kiri atas */
|
// border-top-right-radius: 10px; /* Sudut kiri atas */
|
||||||
border-bottom-right-radius: 10px;
|
// border-bottom-right-radius: 10px;
|
||||||
}
|
// }
|
||||||
tbody td:first-child {
|
// tbody td:first-child {
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
border-top-left-radius: 10px; /* Sudut kiri atas */
|
// border-top-left-radius: 10px; /* Sudut kiri atas */
|
||||||
border-bottom-left-radius: 10px;
|
// border-bottom-left-radius: 10px;
|
||||||
}
|
// }
|
||||||
tbody td:last-child {
|
// tbody td:last-child {
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
border-top-right-radius: 10px; /* Sudut kiri atas */
|
// border-top-right-radius: 10px; /* Sudut kiri atas */
|
||||||
border-bottom-right-radius: 10px;
|
// border-bottom-right-radius: 10px;
|
||||||
}
|
// }
|
||||||
`,
|
`,
|
||||||
checkbox &&
|
checkbox &&
|
||||||
css`
|
css`
|
||||||
|
|
@ -440,7 +461,14 @@ export const TableList: React.FC<any> = ({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!disabledHeadTable ? (
|
{!disabledHeadTable ? (
|
||||||
<thead className="rounded-md overflow-hidden text-md bg-second group/head text-md uppercase text-gray-700 sticky top-0">
|
<thead
|
||||||
|
className={cx(
|
||||||
|
"table-head-list overflow-hidden text-md bg-primary text-white group/head text-md uppercase sticky top-0",
|
||||||
|
css`
|
||||||
|
z-index: 1;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr
|
<tr
|
||||||
key={`${headerGroup.id}`}
|
key={`${headerGroup.id}`}
|
||||||
|
|
@ -536,29 +564,38 @@ export const TableList: React.FC<any> = ({
|
||||||
</div>
|
</div>
|
||||||
{isSort ? (
|
{isSort ? (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<FaChevronUp
|
{local?.sort?.[name] ? (
|
||||||
|
<>
|
||||||
|
{local?.sort?.[name] === "asc" ? (
|
||||||
|
<FaSortUp
|
||||||
className={cx(
|
className={cx(
|
||||||
"px-0.5 mx-1 text-[12px]",
|
"text-xs",
|
||||||
local?.sort?.[name] === "asc"
|
local?.sort?.[name] === "asc"
|
||||||
? "text-black"
|
? "text-white"
|
||||||
: "text-gray-500"
|
: "text-primary"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FaChevronDown
|
) : (
|
||||||
|
<FaSortDown
|
||||||
className={cx(
|
className={cx(
|
||||||
"px-0.5 mx-1 text-[12px]",
|
"text-xs",
|
||||||
local?.sort?.[name] === "desc"
|
local?.sort?.[name] === "desc"
|
||||||
? "text-black"
|
? "text-white"
|
||||||
: "text-gray-500"
|
: "text-primary"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<FaSort className=" text-xs text-white" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{resize &&
|
||||||
{headerGroup.headers.length !== index + 1 ? (
|
headerGroup.headers.length !== index + 1 ? (
|
||||||
<div
|
<div
|
||||||
key={`${header.id}-resizer`} // Tambahkan key unik
|
key={`${header.id}-resizer`} // Tambahkan key unik
|
||||||
{...{
|
{...{
|
||||||
|
|
@ -567,15 +604,15 @@ export const TableList: React.FC<any> = ({
|
||||||
onMouseDown: header.getResizeHandler(),
|
onMouseDown: header.getResizeHandler(),
|
||||||
onTouchStart: header.getResizeHandler(),
|
onTouchStart: header.getResizeHandler(),
|
||||||
className: cx(
|
className: cx(
|
||||||
`resizer bg-[#b3c9fe] cursor-e-resize ${
|
`resizer hover:bg-second cursor-e-resize ${
|
||||||
table.options.columnResizeDirection
|
table.options.columnResizeDirection
|
||||||
} ${
|
} ${
|
||||||
header.column.getIsResizing()
|
header.column.getIsResizing()
|
||||||
? "isResizing"
|
? "isResizing bg-second"
|
||||||
: ""
|
: " bg-primary"
|
||||||
}`,
|
}`,
|
||||||
css`
|
css`
|
||||||
width: 1px;
|
width: 5px;
|
||||||
cursor: e-resize !important;
|
cursor: e-resize !important;
|
||||||
`
|
`
|
||||||
),
|
),
|
||||||
|
|
@ -608,7 +645,7 @@ export const TableList: React.FC<any> = ({
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Table.Body className="divide-y border-none bg-white">
|
<Table.Body className="divide-y border-none ">
|
||||||
{table.getRowModel().rows.map((row, idx) => {
|
{table.getRowModel().rows.map((row, idx) => {
|
||||||
const fm_row =
|
const fm_row =
|
||||||
mode === "form" ? local.dataForm?.[idx] : null;
|
mode === "form" ? local.dataForm?.[idx] : null;
|
||||||
|
|
@ -623,7 +660,8 @@ export const TableList: React.FC<any> = ({
|
||||||
vertical-align: ${align};
|
vertical-align: ${align};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
"border-none"
|
"border-none ",
|
||||||
|
style === "UI" ? "even:bg-linear-blue " : ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell: any) => {
|
{row.getVisibleCells().map((cell: any) => {
|
||||||
|
|
@ -737,7 +775,7 @@ export const Pagination: React.FC<any> = ({
|
||||||
local.render();
|
local.render();
|
||||||
}, [page, count]);
|
}, [page, count]);
|
||||||
return (
|
return (
|
||||||
<div className=" border-t border-gray-300 tbl-pagination sticky text-sm bottom-0 right-0 w-full grid grid-cols-3 gap-4 justify-end text-sm bg-white pt-2">
|
<div className=" border-t border-gray-300 p-2 tbl-pagination sticky text-sm bottom-0 right-0 w-full grid grid-cols-3 gap-4 justify-end text-sm ">
|
||||||
<div className="flex flex-row items-center text-gray-600">
|
<div className="flex flex-row items-center text-gray-600">
|
||||||
Showing {local.page * 20 - 19} to{" "}
|
Showing {local.page * 20 - 19} to{" "}
|
||||||
{list.data?.length >= 20
|
{list.data?.length >= 20
|
||||||
|
|
@ -843,7 +881,7 @@ export const PaginationPage: React.FC<any> = ({
|
||||||
local.render();
|
local.render();
|
||||||
}, [page, count]);
|
}, [page, count]);
|
||||||
return (
|
return (
|
||||||
<div className=" tbl-pagination text-sm bottom-0 right-0 w-full grid grid-cols-1 gap-4 justify-center text-sm bg-white pt-2">
|
<div className=" tbl-pagination text-sm bottom-0 right-0 w-full grid grid-cols-1 gap-4 justify-center text-sm pt-2">
|
||||||
<div className="flex flex-row items-center justify-center">
|
<div className="flex flex-row items-center justify-center">
|
||||||
<div className="flex items-center flex-row gap-x-2 sm:mb-0 text-sm">
|
<div className="flex items-center flex-row gap-x-2 sm:mb-0 text-sm">
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,962 @@
|
||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnResizeDirection,
|
||||||
|
ColumnResizeMode,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
SortingState,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { Button, Label, Table } from "flowbite-react";
|
||||||
|
import { HiChevronLeft, HiChevronRight, HiPlus } from "react-icons/hi";
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import { debouncedHandler } from "@/lib/utils/debounceHandler";
|
||||||
|
import { FaSort, FaSortDown, FaSortUp } from "react-icons/fa6";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { init_column } from "./lib/column";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Loader2, Sticker } from "lucide-react";
|
||||||
|
import { InputSearch } from "../ui/input-search";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
|
import { formatMoney } from "../form/field/TypeInput";
|
||||||
|
import { cloneFM } from "@/lib/utils/cloneFm";
|
||||||
|
|
||||||
|
export const TableListBetter: React.FC<any> = ({
|
||||||
|
name,
|
||||||
|
column,
|
||||||
|
align = "center",
|
||||||
|
onLoad,
|
||||||
|
take = 20,
|
||||||
|
header,
|
||||||
|
disabledPagination,
|
||||||
|
disabledHeader,
|
||||||
|
disabledHeadTable,
|
||||||
|
hiddenNoRow,
|
||||||
|
disabledHoverRow,
|
||||||
|
onInit,
|
||||||
|
onCount,
|
||||||
|
fm,
|
||||||
|
mode,
|
||||||
|
feature,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const [data, setData] = useState<any[]>([]);
|
||||||
|
const sideLeft =
|
||||||
|
typeof header?.sideLeft === "function" ? header.sideLeft : null;
|
||||||
|
const sideRight =
|
||||||
|
typeof header?.sideRight === "function" ? header.sideRight : null;
|
||||||
|
type Person = {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
age: number;
|
||||||
|
visits: number;
|
||||||
|
status: string;
|
||||||
|
progress: number;
|
||||||
|
};
|
||||||
|
const checkbox =
|
||||||
|
Array.isArray(feature) && feature?.length
|
||||||
|
? feature.includes("checkbox")
|
||||||
|
: false;
|
||||||
|
|
||||||
|
const local = useLocal({
|
||||||
|
table: null as any,
|
||||||
|
data: [] as any[],
|
||||||
|
dataForm: [] as any[],
|
||||||
|
listData: [] as any[],
|
||||||
|
sort: {} as any,
|
||||||
|
search: null as any,
|
||||||
|
count: 0 as any,
|
||||||
|
addRow: (row: any) => {
|
||||||
|
setData((prev) => [...prev, row]);
|
||||||
|
local.data.push(row);
|
||||||
|
local.render();
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
all: false,
|
||||||
|
partial: [] as any[],
|
||||||
|
},
|
||||||
|
renderRow: (row: any) => {
|
||||||
|
setData((prev) => [...prev, row]);
|
||||||
|
local.data = data;
|
||||||
|
local.render();
|
||||||
|
},
|
||||||
|
removeRow: (row: any) => {
|
||||||
|
setData((prev) => prev.filter((item) => item !== row)); // Update state lokal
|
||||||
|
local.data = local.data.filter((item: any) => item !== row); // Hapus row dari local.data
|
||||||
|
local.render(); // Panggil render untuk memperbarui UI
|
||||||
|
},
|
||||||
|
reload: async () => {
|
||||||
|
toast.info(
|
||||||
|
<>
|
||||||
|
<Loader2
|
||||||
|
className={cx(
|
||||||
|
"h-4 w-4 animate-spin-important",
|
||||||
|
css`
|
||||||
|
animation: spin 1s linear infinite !important;
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{"Loading..."}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
if (Array.isArray(onLoad)) {
|
||||||
|
local.data = onLoad;
|
||||||
|
local.render();
|
||||||
|
setData(onLoad);
|
||||||
|
} else {
|
||||||
|
const res: any = onLoad({
|
||||||
|
search: local.search,
|
||||||
|
sort: local.sort,
|
||||||
|
take,
|
||||||
|
paging: 1,
|
||||||
|
});
|
||||||
|
if (res instanceof Promise) {
|
||||||
|
res.then((e) => {
|
||||||
|
local.data = e;
|
||||||
|
cloneListFM(e);
|
||||||
|
local.render();
|
||||||
|
setData(e);
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.dismiss();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
local.data = res;
|
||||||
|
cloneListFM(res);
|
||||||
|
local.render();
|
||||||
|
setData(res);
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.dismiss();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const cloneListFM = (data: any[]) => {
|
||||||
|
if (mode === "form") {
|
||||||
|
local.dataForm = data.map((e: any) => cloneFM(fm, e));
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
const run = async () => {
|
||||||
|
toast.info(
|
||||||
|
<>
|
||||||
|
<Loader2
|
||||||
|
className={cx(
|
||||||
|
"h-4 w-4 animate-spin-important",
|
||||||
|
css`
|
||||||
|
animation: spin 1s linear infinite !important;
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{"Loading..."}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
if (typeof onCount === "function") {
|
||||||
|
const res = await onCount();
|
||||||
|
local.count = res;
|
||||||
|
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(onLoad)) {
|
||||||
|
local.data = onLoad;
|
||||||
|
cloneListFM(onLoad);
|
||||||
|
local.render();
|
||||||
|
setData(onLoad);
|
||||||
|
} else {
|
||||||
|
const res: any = await onLoad({
|
||||||
|
search: local.search,
|
||||||
|
sort: local.sort,
|
||||||
|
take,
|
||||||
|
paging: 1,
|
||||||
|
});
|
||||||
|
local.data = res;
|
||||||
|
cloneListFM(res);
|
||||||
|
local.render();
|
||||||
|
setData(res);
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.dismiss();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (typeof onInit === "function") {
|
||||||
|
onInit(local);
|
||||||
|
}
|
||||||
|
run();
|
||||||
|
}, []);
|
||||||
|
const defaultColumns: ColumnDef<Person>[] = init_column(column);
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columns] = React.useState<typeof defaultColumns>(() =>
|
||||||
|
checkbox
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: "select",
|
||||||
|
width: 10,
|
||||||
|
header: ({ table }) => (
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={table.getIsAllRowsSelected()}
|
||||||
|
onClick={(e) => {
|
||||||
|
table.getToggleAllRowsSelectedHandler();
|
||||||
|
const handler = table.getToggleAllRowsSelectedHandler();
|
||||||
|
handler(e); // Pastikan ini memanggil fungsi handler yang benar
|
||||||
|
local.selection.all = !local.selection.all;
|
||||||
|
local.render();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const findCheck = (row: any) => {
|
||||||
|
if (row.getIsSelected()) return true;
|
||||||
|
const data: any = row.original;
|
||||||
|
const res = local.selection.partial.find((e) => e === data?.id);
|
||||||
|
return res ? true : false;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="px-0.5 items-center justify-center flex flex-row">
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={findCheck(row)}
|
||||||
|
onClick={(e) => {
|
||||||
|
const handler = row.getToggleSelectedHandler();
|
||||||
|
handler(e); // Pastikan ini memanggil fungsi handler yang benar
|
||||||
|
const data: any = row.original;
|
||||||
|
const checked = local.selection.all
|
||||||
|
? true
|
||||||
|
: local.selection.partial.find((e) => e === data?.id);
|
||||||
|
if (!checked) {
|
||||||
|
local.selection.partial.push(data?.id);
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
local.selection.partial.find((e) => e === data?.id)
|
||||||
|
) {
|
||||||
|
local.selection.partial =
|
||||||
|
local.selection.partial.filter(
|
||||||
|
(e: any) => e !== data?.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
local.selection.all = false;
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
...defaultColumns,
|
||||||
|
]
|
||||||
|
: [...defaultColumns]
|
||||||
|
);
|
||||||
|
const [columnResizeMode, setColumnResizeMode] =
|
||||||
|
React.useState<ColumnResizeMode>("onChange");
|
||||||
|
|
||||||
|
const [columnResizeDirection, setColumnResizeDirection] =
|
||||||
|
React.useState<ColumnResizeDirection>("ltr");
|
||||||
|
// Create the table and pass your options
|
||||||
|
useEffect(() => {
|
||||||
|
setData(local.data);
|
||||||
|
}, [local.data.length]);
|
||||||
|
const paginationConfig = disabledPagination
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
};
|
||||||
|
const [pagination, setPagination] = React.useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
const table = useReactTable({
|
||||||
|
data: data,
|
||||||
|
columnResizeMode,
|
||||||
|
pageCount: Math.ceil(local.count / 20),
|
||||||
|
manualPagination: true,
|
||||||
|
columnResizeDirection,
|
||||||
|
columns,
|
||||||
|
enableRowSelection: true,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
initialState: {
|
||||||
|
pagination: {
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 20, //custom default page size
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
pagination,
|
||||||
|
sorting,
|
||||||
|
},
|
||||||
|
...paginationConfig,
|
||||||
|
});
|
||||||
|
local.table = table;
|
||||||
|
|
||||||
|
// Manage your own state
|
||||||
|
const [state, setState] = React.useState(table.initialState);
|
||||||
|
|
||||||
|
// Override the state managers for the table to your own
|
||||||
|
table.setOptions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
state,
|
||||||
|
onStateChange: setState,
|
||||||
|
debugTable: state.pagination.pageIndex > 2,
|
||||||
|
}));
|
||||||
|
const handleSearch = useCallback(
|
||||||
|
debouncedHandler(() => {
|
||||||
|
local.reload();
|
||||||
|
}, 1000),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="tbl-wrapper flex flex-grow flex-col">
|
||||||
|
{!disabledHeader ? (
|
||||||
|
<div className="head-tbl-list block items-start justify-between px-4 py-4 sm:flex">
|
||||||
|
<div className="flex flex-row items-end">
|
||||||
|
<div className="sm:flex flex flex-col space-y-2">
|
||||||
|
<div className="flex">
|
||||||
|
{sideLeft ? (
|
||||||
|
sideLeft(local)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link href={"/new"}>
|
||||||
|
<Button className="bg-primary">
|
||||||
|
<div className="flex items-center gap-x-0.5">
|
||||||
|
<HiPlus className="text-xl" />
|
||||||
|
<span className="capitalize">Add {name}</span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ml-auto flex items-center flex-row">
|
||||||
|
<div className="tbl-search hidden items-center sm:mb-0 sm:flex sm:divide-x sm:divide-gray-100">
|
||||||
|
<form
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await local.reload();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label htmlFor="users-search" className="sr-only">
|
||||||
|
Search
|
||||||
|
</Label>
|
||||||
|
<div className="relative lg:w-56">
|
||||||
|
<InputSearch
|
||||||
|
// className="bg-white search text-xs "
|
||||||
|
id="users-search"
|
||||||
|
name="users-search"
|
||||||
|
placeholder={`Search`}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
local.search = value;
|
||||||
|
local.render();
|
||||||
|
handleSearch();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="flex">{sideRight ? sideRight(local) : <></>}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col flex-grow">
|
||||||
|
<div className="container-table overflow-auto relative flex-grow flex-row">
|
||||||
|
<div className="tbl absolute top-0 left-0 inline-block flex-grow w-full h-full align-middle">
|
||||||
|
<div className="relative">
|
||||||
|
<Table
|
||||||
|
className={cx(
|
||||||
|
"min-w-full divide-y divide-gray-200 table-bg",
|
||||||
|
css`
|
||||||
|
// thead th:first-child {
|
||||||
|
// overflow: hidden;
|
||||||
|
// border-top-left-radius: 10px; /* Sudut kiri atas */
|
||||||
|
// border-bottom-left-radius: 10px;
|
||||||
|
// }
|
||||||
|
// thead th:last-child {
|
||||||
|
// overflow: hidden;
|
||||||
|
// border-top-right-radius: 10px; /* Sudut kiri atas */
|
||||||
|
// border-bottom-right-radius: 10px;
|
||||||
|
// }
|
||||||
|
// tbody td:first-child {
|
||||||
|
// overflow: hidden;
|
||||||
|
// border-top-left-radius: 10px; /* Sudut kiri atas */
|
||||||
|
// border-bottom-left-radius: 10px;
|
||||||
|
// }
|
||||||
|
// tbody td:last-child {
|
||||||
|
// overflow: hidden;
|
||||||
|
// border-top-right-radius: 10px; /* Sudut kiri atas */
|
||||||
|
// border-bottom-right-radius: 10px;
|
||||||
|
// }
|
||||||
|
`,
|
||||||
|
checkbox &&
|
||||||
|
css`
|
||||||
|
.table-header-tbl > th:first-child {
|
||||||
|
width: 20px !important; /* Atur lebar sesuai kebutuhan */
|
||||||
|
text-align: center;
|
||||||
|
min-width: 40px;
|
||||||
|
max-width: 40px;
|
||||||
|
}
|
||||||
|
.table-row-element > td:first-child {
|
||||||
|
width: 20px !important; /* Atur lebar sesuai kebutuhan */
|
||||||
|
text-align: center;
|
||||||
|
min-width: 40px;
|
||||||
|
max-width: 40px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!disabledHeadTable ? (
|
||||||
|
<thead className="table-head-list overflow-hidden text-md bg-primary text-white group/head text-md uppercase sticky top-0">
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<tr
|
||||||
|
key={`${headerGroup.id}`}
|
||||||
|
className={"table-header-tbl"}
|
||||||
|
>
|
||||||
|
{headerGroup.headers.map((header, index) => {
|
||||||
|
const name = header.column.id;
|
||||||
|
const col = column.find(
|
||||||
|
(e: any) => e?.name === name
|
||||||
|
);
|
||||||
|
const isSort =
|
||||||
|
name === "select"
|
||||||
|
? false
|
||||||
|
: typeof col?.sortable === "boolean"
|
||||||
|
? col.sortable
|
||||||
|
: true;
|
||||||
|
const resize =
|
||||||
|
name === "select"
|
||||||
|
? false
|
||||||
|
: typeof col?.resize === "boolean"
|
||||||
|
? col.resize
|
||||||
|
: true;
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
{...{
|
||||||
|
style: {
|
||||||
|
width: !resize
|
||||||
|
? `${col?.width}px`
|
||||||
|
: name === "select"
|
||||||
|
? `${5}px`
|
||||||
|
: col?.width
|
||||||
|
? header.getSize() < col?.width
|
||||||
|
? `${col.width}px`
|
||||||
|
: header.getSize()
|
||||||
|
: header.getSize(),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
key={header.id}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
className={cx(
|
||||||
|
"relative px-2 py-2 text-sm py-1 uppercase",
|
||||||
|
name === "select" &&
|
||||||
|
css`
|
||||||
|
max-width: 5px;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
key={`${header.id}-label`}
|
||||||
|
{...{
|
||||||
|
style: col?.width
|
||||||
|
? {
|
||||||
|
minWidth: `${col.width}px`,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSort) {
|
||||||
|
const sort = local?.sort?.[name];
|
||||||
|
const mode =
|
||||||
|
sort === "desc"
|
||||||
|
? null
|
||||||
|
: sort === "asc"
|
||||||
|
? "desc"
|
||||||
|
: "asc";
|
||||||
|
local.sort = mode
|
||||||
|
? {
|
||||||
|
[name]: mode,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
local.render();
|
||||||
|
|
||||||
|
local.reload();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"flex flex-grow flex-row flex-grow select-none items-center flex-row text-base text-nowrap",
|
||||||
|
isSort ? " cursor-pointer" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center flex-grow text-sm capitalize",
|
||||||
|
name === "select" ? "justify-center" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isSort ? (
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
{local?.sort?.[name] ? (
|
||||||
|
<>
|
||||||
|
{local?.sort?.[name] === "asc" ? (
|
||||||
|
<FaSortUp
|
||||||
|
className={cx(
|
||||||
|
"text-xs",
|
||||||
|
local?.sort?.[name] === "asc"
|
||||||
|
? "text-white"
|
||||||
|
: "text-primary"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FaSortDown
|
||||||
|
className={cx(
|
||||||
|
"text-xs",
|
||||||
|
local?.sort?.[name] === "desc"
|
||||||
|
? "text-white"
|
||||||
|
: "text-primary"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<FaSort className=" text-xs text-white" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{resize &&
|
||||||
|
headerGroup.headers.length !== index + 1 ? (
|
||||||
|
<div
|
||||||
|
key={`${header.id}-resizer`} // Tambahkan key unik
|
||||||
|
{...{
|
||||||
|
onDoubleClick: () =>
|
||||||
|
header.column.resetSize(),
|
||||||
|
onMouseDown: header.getResizeHandler(),
|
||||||
|
onTouchStart: header.getResizeHandler(),
|
||||||
|
className: cx(
|
||||||
|
`resizer hover:bg-second cursor-e-resize ${
|
||||||
|
table.options.columnResizeDirection
|
||||||
|
} ${
|
||||||
|
header.column.getIsResizing()
|
||||||
|
? "isResizing bg-second"
|
||||||
|
: " bg-primary"
|
||||||
|
}`,
|
||||||
|
css`
|
||||||
|
width: 1px;
|
||||||
|
cursor: e-resize !important;
|
||||||
|
`
|
||||||
|
),
|
||||||
|
style: {
|
||||||
|
transform:
|
||||||
|
columnResizeMode === "onEnd" &&
|
||||||
|
header.column.getIsResizing()
|
||||||
|
? `translateX(${
|
||||||
|
(table.options
|
||||||
|
.columnResizeDirection ===
|
||||||
|
"rtl"
|
||||||
|
? -1
|
||||||
|
: 1) *
|
||||||
|
(table.getState()
|
||||||
|
.columnSizingInfo
|
||||||
|
.deltaOffset ?? 0)
|
||||||
|
}px)`
|
||||||
|
: "",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
) : null}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Table.Body className="divide-y border-none ">
|
||||||
|
{table.getRowModel().rows.map((row, idx) => {
|
||||||
|
const fm_row =
|
||||||
|
mode === "form" ? local.dataForm?.[idx] : null;
|
||||||
|
return (
|
||||||
|
<Table.Row
|
||||||
|
key={row.id}
|
||||||
|
className={cx(
|
||||||
|
disabledHoverRow ? "" : "hover:bg-gray-100",
|
||||||
|
css`
|
||||||
|
height: 44px;
|
||||||
|
> td {
|
||||||
|
vertical-align: ${align};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"border-none even:bg-linear-blue "
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell: any) => {
|
||||||
|
const ctx = cell.getContext();
|
||||||
|
const param = {
|
||||||
|
row: row.original,
|
||||||
|
name: get(ctx, "column.columnDef.accessorKey"),
|
||||||
|
cell,
|
||||||
|
idx,
|
||||||
|
tbl: local,
|
||||||
|
fm_row: fm_row,
|
||||||
|
onChange,
|
||||||
|
};
|
||||||
|
const head = column.find(
|
||||||
|
(e: any) =>
|
||||||
|
e?.name ===
|
||||||
|
get(ctx, "column.columnDef.accessorKey")
|
||||||
|
);
|
||||||
|
const renderData =
|
||||||
|
typeof head?.renderCell === "function"
|
||||||
|
? head.renderCell(param)
|
||||||
|
: flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext()
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Table.Cell
|
||||||
|
className={cx(
|
||||||
|
"text-md px-2 py-1 whitespace-nowrap text-gray-900 items-start",
|
||||||
|
name === "select"
|
||||||
|
? css`
|
||||||
|
width: 5px;
|
||||||
|
`
|
||||||
|
: ``
|
||||||
|
)}
|
||||||
|
key={cell.id}
|
||||||
|
>
|
||||||
|
{renderData}
|
||||||
|
</Table.Cell>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Table.Row>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Table.Body>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!hiddenNoRow && !table.getRowModel().rows?.length && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex-1 w-full absolute inset-0 flex flex-col items-center justify-center",
|
||||||
|
css`
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="max-w-[15%] flex flex-col items-center">
|
||||||
|
<Sticker size={35} strokeWidth={1} />
|
||||||
|
<div className="pt-1 text-center">No Data</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PaginationPage
|
||||||
|
list={local}
|
||||||
|
count={local.count}
|
||||||
|
onNextPage={() => table.nextPage()}
|
||||||
|
onPrevPage={() => table.previousPage()}
|
||||||
|
disabledNextPage={!table.getCanNextPage()}
|
||||||
|
disabledPrevPage={!table.getCanPreviousPage()}
|
||||||
|
page={table.getState().pagination.pageIndex + 1}
|
||||||
|
setPage={(page: any) => {
|
||||||
|
setPagination({
|
||||||
|
pageIndex: page,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
countPage={table.getPageCount()}
|
||||||
|
countData={local.data.length}
|
||||||
|
take={take}
|
||||||
|
onChangePage={(page: number) => {
|
||||||
|
table.setPageIndex(page);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Pagination: React.FC<any> = ({
|
||||||
|
onNextPage,
|
||||||
|
onPrevPage,
|
||||||
|
disabledNextPage,
|
||||||
|
disabledPrevPage,
|
||||||
|
page,
|
||||||
|
count,
|
||||||
|
list,
|
||||||
|
setPage,
|
||||||
|
onChangePage,
|
||||||
|
}) => {
|
||||||
|
const local = useLocal({
|
||||||
|
page: 1 as any,
|
||||||
|
pagination: [] as any,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
local.page = page;
|
||||||
|
local.pagination = getPagination(page, Math.ceil(count / 20));
|
||||||
|
local.render();
|
||||||
|
}, [page, count]);
|
||||||
|
return (
|
||||||
|
<div className=" border-t border-gray-300 tbl-pagination sticky text-sm bottom-0 right-0 w-full grid grid-cols-3 gap-4 justify-end text-sm pt-2">
|
||||||
|
<div className="flex flex-row items-center text-gray-600">
|
||||||
|
Showing {local.page * 20 - 19} to{" "}
|
||||||
|
{list.data?.length >= 20
|
||||||
|
? local.page * 20
|
||||||
|
: local.page === 1 && Math.ceil(count / 20) === 1
|
||||||
|
? list.data?.length
|
||||||
|
: local.page * 20 - 19 + list.data?.length}{" "}
|
||||||
|
of {formatMoney(getNumber(count))} results
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-center">
|
||||||
|
<div>
|
||||||
|
<nav
|
||||||
|
className="isolate inline-flex -space-x-px flex flex-row items-center gap-x-2"
|
||||||
|
aria-label="Pagination"
|
||||||
|
>
|
||||||
|
{local.pagination.map((e: any, idx: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={"page_" + idx}
|
||||||
|
onClick={() => {
|
||||||
|
if (e?.label !== "...") {
|
||||||
|
local.page = getNumber(e?.label);
|
||||||
|
local.render();
|
||||||
|
onChangePage(local.page - 1);
|
||||||
|
setPage(local.page - 1);
|
||||||
|
list.reload();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"text-sm px-2 py-1",
|
||||||
|
e.active
|
||||||
|
? "relative z-10 inline-flex items-center bg-primary font-semibold text-white rounded-md"
|
||||||
|
: e?.label === "..."
|
||||||
|
? "relative z-10 inline-flex items-center font-semibold text-gray-800 rounded-md"
|
||||||
|
: "cursor-pointer relative z-10 inline-flex items-center hover:bg-gray-100 font-semibold text-gray-800 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{e?.label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-end">
|
||||||
|
<div className="flex items-center flex-row gap-x-2 sm:mb-0 text-sm">
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!disabledPrevPage) {
|
||||||
|
onPrevPage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center gap-x-2 justify-center rounded p-1 ",
|
||||||
|
disabledPrevPage
|
||||||
|
? "text-gray-200 border-gray-200 border px-2"
|
||||||
|
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border px-2"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<HiChevronLeft className="text-sm" />
|
||||||
|
{/* <span>Previous</span> */}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!disabledNextPage) {
|
||||||
|
onNextPage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center gap-x-2 justify-center rounded p-1 ",
|
||||||
|
disabledNextPage
|
||||||
|
? "text-gray-200 border-gray-200 border px-2"
|
||||||
|
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border px-2"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* <span>Next</span> */}
|
||||||
|
<HiChevronRight className="text-sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const PaginationPage: React.FC<any> = ({
|
||||||
|
onNextPage,
|
||||||
|
onPrevPage,
|
||||||
|
disabledNextPage,
|
||||||
|
disabledPrevPage,
|
||||||
|
page,
|
||||||
|
count,
|
||||||
|
list,
|
||||||
|
take,
|
||||||
|
setPage,
|
||||||
|
onChangePage,
|
||||||
|
}) => {
|
||||||
|
const local = useLocal({
|
||||||
|
page: 1 as any,
|
||||||
|
pagination: [] as any,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
local.page = page;
|
||||||
|
local.pagination = getPagination(page, Math.ceil(count / take));
|
||||||
|
local.render();
|
||||||
|
}, [page, count]);
|
||||||
|
return (
|
||||||
|
<div className="py-2 tbl-pagination text-sm bottom-0 right-0 w-full grid grid-cols-1 gap-4 justify-center text-sm">
|
||||||
|
<div className="flex flex-row items-center justify-center">
|
||||||
|
<div className="flex items-center flex-row gap-x-2 sm:mb-0 text-sm">
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!disabledPrevPage) {
|
||||||
|
onPrevPage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center gap-x-2 justify-center rounded-full p-2 text-md",
|
||||||
|
disabledPrevPage
|
||||||
|
? "text-gray-200 border-gray-200 border "
|
||||||
|
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border "
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<HiChevronLeft />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-center">
|
||||||
|
<div>
|
||||||
|
<nav
|
||||||
|
className="isolate inline-flex -space-x-px flex flex-row items-center gap-x-2"
|
||||||
|
aria-label="Pagination"
|
||||||
|
>
|
||||||
|
{local.pagination.map((e: any, idx: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={"page_" + idx}
|
||||||
|
onClick={() => {
|
||||||
|
if (e?.label !== "...") {
|
||||||
|
local.page = getNumber(e?.label);
|
||||||
|
local.render();
|
||||||
|
onChangePage(local.page - 1);
|
||||||
|
setPage(local.page - 1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"text-md px-2.5 py-1",
|
||||||
|
e.active
|
||||||
|
? "relative z-10 inline-flex items-center bg-primary font-semibold text-white rounded-full"
|
||||||
|
: e?.label === "..."
|
||||||
|
? "relative z-10 inline-flex items-center font-semibold text-gray-800 rounded-full"
|
||||||
|
: "cursor-pointer relative z-10 inline-flex items-center hover:bg-gray-100 font-semibold text-gray-800 rounded-full"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{e?.label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!disabledNextPage) {
|
||||||
|
onNextPage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center gap-x-2 justify-center rounded-full p-2 ",
|
||||||
|
disabledNextPage
|
||||||
|
? "text-gray-200 border-gray-200 border"
|
||||||
|
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border "
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<HiChevronRight className="text-md" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPagination = (currentPage: number, totalPages: number) => {
|
||||||
|
const pagination: { label: string; active: boolean }[] = [];
|
||||||
|
const maxVisible = 5; // Jumlah maksimal elemen yang ditampilkan
|
||||||
|
const halfRange = Math.floor((maxVisible - 3) / 2);
|
||||||
|
|
||||||
|
if (totalPages <= maxVisible) {
|
||||||
|
// Jika total halaman lebih kecil dari batas, tampilkan semua halaman
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
pagination.push({ label: i.toString(), active: i === currentPage });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pagination.push({ label: "1", active: currentPage === 1 }); // Halaman pertama selalu ada
|
||||||
|
|
||||||
|
if (currentPage > halfRange + 2) {
|
||||||
|
pagination.push({ label: "...", active: false }); // Awal titik-titik
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPage = Math.max(2, currentPage - halfRange);
|
||||||
|
const endPage = Math.min(totalPages - 1, currentPage + halfRange);
|
||||||
|
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pagination.push({ label: i.toString(), active: i === currentPage });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage < totalPages - halfRange - 1) {
|
||||||
|
pagination.push({ label: "...", active: false }); // Akhir titik-titik
|
||||||
|
}
|
||||||
|
|
||||||
|
pagination.push({
|
||||||
|
label: totalPages.toString(),
|
||||||
|
active: currentPage === totalPages,
|
||||||
|
}); // Halaman terakhir selalu ada
|
||||||
|
}
|
||||||
|
|
||||||
|
return pagination;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import { TableList } from "./TableList";
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { TabHeaderBetter } from "../tablist/TabHeaderBetter";
|
||||||
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
|
export const TableUI: React.FC<any> = ({
|
||||||
|
name,
|
||||||
|
column,
|
||||||
|
align = "center",
|
||||||
|
onLoad,
|
||||||
|
take = 20,
|
||||||
|
header,
|
||||||
|
disabledPagination,
|
||||||
|
disabledHeader,
|
||||||
|
disabledHeadTable,
|
||||||
|
hiddenNoRow,
|
||||||
|
disabledHoverRow,
|
||||||
|
onInit,
|
||||||
|
onCount,
|
||||||
|
fm,
|
||||||
|
mode,
|
||||||
|
feature,
|
||||||
|
onChange,
|
||||||
|
delete_name,
|
||||||
|
title,
|
||||||
|
tab,
|
||||||
|
onTab,
|
||||||
|
}) => {
|
||||||
|
const local = useLocal({
|
||||||
|
tab: get(tab, "[0].id"),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col flex-grow">
|
||||||
|
<div className="w-full flex flex-row p-4 py-6 pr-6 pl-3 text-2xl font-bold">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col flex-grow bg-card-layer rounded-lg border border-gray-300 shadow-md shadow-gray-300 overflow-hidden">
|
||||||
|
<div className="flex flex-col flex-grow">
|
||||||
|
{tab?.length && (
|
||||||
|
<div className="flex flex-row justify-start">
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<TabHeaderBetter
|
||||||
|
disabledPagination={true}
|
||||||
|
onLabel={(row: any) => {
|
||||||
|
console.log({ row });
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row items-center gap-x-2 font-bold">
|
||||||
|
<div className="text-3xl">
|
||||||
|
{getNumber(row?.count) > 999
|
||||||
|
? "99+"
|
||||||
|
: getNumber(row?.count)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-start ">
|
||||||
|
<div className="text-start">Total</div>
|
||||||
|
<div>{row.name}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onValue={(row: any) => {
|
||||||
|
return row.id;
|
||||||
|
}}
|
||||||
|
onLoad={tab}
|
||||||
|
onChange={(tab: any) => {
|
||||||
|
local.tab = tab?.id;
|
||||||
|
local.render();
|
||||||
|
if (typeof onTab === "function") {
|
||||||
|
onTab(local.tab);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
tabContent={(data: any) => {
|
||||||
|
return <></>;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col bg-white mt-[-1px] flex-grow">
|
||||||
|
<div className="w-full flex flex-row flex-grow overflow-hidden ">
|
||||||
|
<TableList
|
||||||
|
name={name}
|
||||||
|
header={header}
|
||||||
|
column={column}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onCount={onCount}
|
||||||
|
onInit={onInit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
"use client";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
|
||||||
|
export const TabHeaderBetter: React.FC<any> = ({
|
||||||
|
name,
|
||||||
|
column,
|
||||||
|
onLabel,
|
||||||
|
onValue,
|
||||||
|
onLoad,
|
||||||
|
take = 20,
|
||||||
|
header,
|
||||||
|
tabContent,
|
||||||
|
disabledPagination,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const sideRight =
|
||||||
|
typeof header?.sideRight === "function" ? header.sideRight : null;
|
||||||
|
const local = useLocal({
|
||||||
|
data: [] as any[],
|
||||||
|
sort: {} as any,
|
||||||
|
search: null as any,
|
||||||
|
reload: async () => {
|
||||||
|
const res: any = onLoad({
|
||||||
|
search: local.search,
|
||||||
|
sort: local.sort,
|
||||||
|
take,
|
||||||
|
paging: 1,
|
||||||
|
});
|
||||||
|
if (res instanceof Promise) {
|
||||||
|
res.then((e) => {
|
||||||
|
local.data = e;
|
||||||
|
local.render();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
local.data = res;
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
local.data = onLoad;
|
||||||
|
local.render();
|
||||||
|
}, []);
|
||||||
|
if (!local.data?.length) return <></>;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row w-full">
|
||||||
|
<Tabs
|
||||||
|
className="flex flex-col w-full"
|
||||||
|
defaultValue={onValue(local.data?.[0])}
|
||||||
|
>
|
||||||
|
<TabsList className="flex flex-row relative w-full bg-transparent p-0 rounded-none">
|
||||||
|
{Array.isArray(onLoad) &&
|
||||||
|
onLoad.map((e, idx) => {
|
||||||
|
return (
|
||||||
|
<TabsTrigger
|
||||||
|
value={onValue(e)}
|
||||||
|
onClick={() => {
|
||||||
|
if (typeof onChange === "function") {
|
||||||
|
onChange(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"p-1.5 px-4 border text-sm text-gray-500 border-none mr-0 rounded-none focus-visible:ring-0 data-[state=active]:ring-0 transition-none bg-card-layer data-[state=active]:bg-white data-[state=active]:text-primary data-[state=active]:shadow-none data-[state=active]:border-none",
|
||||||
|
idx === 0
|
||||||
|
? "data-[state=active]:slanted-edge data-[state=active]:pr-8 rounded-tl-sm"
|
||||||
|
: "data-[state=active]:parallelogram pr-8 data-[state=active]:pl-8",
|
||||||
|
!idx ? "" : idx++ === local.data.length ? "" : ""
|
||||||
|
)}
|
||||||
|
key={onValue(e)}
|
||||||
|
>
|
||||||
|
{onLabel(e)}
|
||||||
|
<div className="triangle"></div>
|
||||||
|
</TabsTrigger>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TabsList>
|
||||||
|
{local.data.map((e) => {
|
||||||
|
return (
|
||||||
|
<TabsContent value={onValue(e)} key={onValue(e) + "_tabcontent"}>
|
||||||
|
<div className="flex flex-row flex-grow w-full h-full">
|
||||||
|
{tabContent(e)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||||
|
import { cn } from "@/lib/utils/utils";
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
));
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Progress };
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||||
|
import { cn } from "@/lib/utils/utils";
|
||||||
|
|
||||||
|
const ScrollArea = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn("relative overflow-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] relative">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-full">{children}</div>
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
));
|
||||||
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
const ScrollBar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
|
ref={ref}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"hover:bg-gray-100/50 flex touch-none select-none transition-colors ",
|
||||||
|
orientation === "vertical" &&
|
||||||
|
"h-full w-2.5 border-l border-l-transparent ",
|
||||||
|
orientation === "horizontal" &&
|
||||||
|
"h-2.5 flex-col border-t border-t-transparent ",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border bg-gray-300" />
|
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
));
|
||||||
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar };
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function CustomScroll({ children, className }: any) {
|
||||||
|
return (
|
||||||
|
<div className={`relative ${className}`}>
|
||||||
|
{/* Kontainer Scroll */}
|
||||||
|
<div className="relative w-full h-full overflow-hidden rounded-lg">
|
||||||
|
<div className="scroll-area w-full h-full overflow-y-auto">
|
||||||
|
{/* Konten Scroll */}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scrollbar Manual */}
|
||||||
|
<div className="scrollbar-track absolute top-0 right-0 w-2 h-full bg-gray-200 rounded-lg">
|
||||||
|
<div className="scrollbar-thumb w-2 bg-gray-500 rounded-lg transition-transform duration-200 hover:bg-gray-600"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
import { cn } from "@/lib/utils/utils";
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider;
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root;
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
));
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||||
|
|
@ -10,8 +10,11 @@
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.2",
|
"@radix-ui/react-alert-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-checkbox": "^1.1.3",
|
"@radix-ui/react-checkbox": "^1.1.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
|
"@radix-ui/react-progress": "^1.1.1",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
|
"@radix-ui/react-tooltip": "^1.1.7",
|
||||||
"@react-pdf/renderer": "^4.1.5",
|
"@react-pdf/renderer": "^4.1.5",
|
||||||
"@tanstack/react-table": "^8.20.5",
|
"@tanstack/react-table": "^8.20.5",
|
||||||
"@tiptap/extension-color": "^2.11.2",
|
"@tiptap/extension-color": "^2.11.2",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue