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 { TypeInput } from "./field/TypeInput";
|
||||
import { TypeUpload } from "./field/TypeUpload";
|
||||
import { FieldUploadMulti } from "./field/TypeUploadMulti";
|
||||
import { TypeRichText } from "./field/TypeRichText";
|
||||
import { TypeTag } from "./field/TypeTag";
|
||||
import get from "lodash.get";
|
||||
|
|
@ -23,6 +22,7 @@ export const Field: React.FC<any> = ({
|
|||
hidden_label,
|
||||
onChange,
|
||||
className,
|
||||
classField,
|
||||
style,
|
||||
prefix,
|
||||
suffix,
|
||||
|
|
@ -98,11 +98,16 @@ export const Field: React.FC<any> = ({
|
|||
{!hidden_label ? (
|
||||
<label
|
||||
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]" : ""
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{required ? (
|
||||
<span className="flex flex-row px-0.5 text-red-500">*</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</label>
|
||||
) : (
|
||||
<></>
|
||||
|
|
@ -137,7 +142,8 @@ export const Field: React.FC<any> = ({
|
|||
["upload"].includes(type) &&
|
||||
css`
|
||||
padding: 0px !important;
|
||||
`
|
||||
`,
|
||||
classField
|
||||
)}
|
||||
>
|
||||
{before && (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"use client";
|
||||
import { ScrollArea } from "../ui/scroll-area";
|
||||
import { Form } from "./Form";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
|
@ -21,50 +22,52 @@ export const FormBetter: React.FC<any> = ({
|
|||
return (
|
||||
<div className="flex flex-col flex-grow gap-y-3">
|
||||
{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)}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div className="w-full flex-grow flex flex-row rounded-lg overflow-hidden">
|
||||
<div className="w-full flex flex-row flex-grow bg-white rounded-lg border border-gray-300 relative overflow-y-scroll">
|
||||
<Form
|
||||
{...{
|
||||
children,
|
||||
header,
|
||||
onTitle,
|
||||
onLoad,
|
||||
onSubmit,
|
||||
onFooter,
|
||||
showResize,
|
||||
mode,
|
||||
className: cx(className, "absolute top-0 left-0 w-full"),
|
||||
onInit: (form: any) => {
|
||||
setFM(form);
|
||||
<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">
|
||||
<ScrollArea className="flex-grow">
|
||||
<Form
|
||||
{...{
|
||||
children,
|
||||
header,
|
||||
onTitle,
|
||||
onLoad,
|
||||
onSubmit,
|
||||
onFooter,
|
||||
showResize,
|
||||
mode,
|
||||
className: cx(className, "top-0 left-0 w-full"),
|
||||
onInit: (form: any) => {
|
||||
setFM(form);
|
||||
|
||||
const originalRender = form.render;
|
||||
const originalRender = form.render;
|
||||
|
||||
// Buat versi baru dari `local.render`
|
||||
form.render = () => {
|
||||
// Panggil fungsi asli
|
||||
originalRender();
|
||||
// Buat versi baru dari `local.render`
|
||||
form.render = () => {
|
||||
// Panggil fungsi asli
|
||||
originalRender();
|
||||
|
||||
// Tambahkan logika tambahan untuk sinkronisasi
|
||||
setFM({
|
||||
...form,
|
||||
submit: form.submit,
|
||||
render: form.render,
|
||||
data: form.data,
|
||||
});
|
||||
};
|
||||
form.render();
|
||||
if (typeof onInit === "function") {
|
||||
onInit(form);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
// Tambahkan logika tambahan untuk sinkronisasi
|
||||
setFM({
|
||||
...form,
|
||||
submit: form.submit,
|
||||
render: form.render,
|
||||
data: form.data,
|
||||
});
|
||||
};
|
||||
form.render();
|
||||
if (typeof onInit === "function") {
|
||||
onInit(form);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</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;
|
||||
if (url instanceof File) {
|
||||
ural = `${URL.createObjectURL(url)}.${url.name.split(".").pop()}`;
|
||||
|
|
@ -211,7 +217,7 @@ export const FilePreview = ({ url }: { url: any }) => {
|
|||
{file.extension && (
|
||||
<div
|
||||
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",
|
||||
css`
|
||||
&:hover {
|
||||
|
|
@ -222,7 +228,8 @@ export const FilePreview = ({ url }: { url: any }) => {
|
|||
// border-bottom: 1px solid #1c4ed8;
|
||||
// outline: 1px solid #1c4ed8;
|
||||
}
|
||||
`
|
||||
`,
|
||||
disabled ? "bg-transparent" : "bg-white"
|
||||
)}
|
||||
onClick={() => {
|
||||
let _url: any =
|
||||
|
|
@ -231,8 +238,10 @@ export const FilePreview = ({ url }: { url: any }) => {
|
|||
window.open(_url, "_blank");
|
||||
}}
|
||||
>
|
||||
<div className="h-[30px] flex flex-row items-center">{content}</div>
|
||||
<div className="text-xs filename">{file?.name}</div>
|
||||
<div className="flex flex-row gap-x-1 items-center">
|
||||
<div className="h-[30px] flex flex-row items-center">{content}</div>
|
||||
<div className="text-xs filename">{file?.name}</div>
|
||||
</div>
|
||||
|
||||
<div className="ml-2">
|
||||
<ExternalLink size="12px" />
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Textarea } from "../../ui/text-area";
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import tinycolor from "tinycolor2";
|
||||
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 { getNumber } from "@/lib/utils/getNumber";
|
||||
import MaskedInput from "../../ui/MaskedInput";
|
||||
|
|
@ -34,6 +34,7 @@ export const TypeInput: React.FC<any> = ({
|
|||
const input = useLocal({
|
||||
value: 0 as any,
|
||||
ref: null as any,
|
||||
show_pass: false as boolean,
|
||||
open: false,
|
||||
});
|
||||
const meta = useLocal({
|
||||
|
|
@ -319,6 +320,10 @@ export const TypeInput: React.FC<any> = ({
|
|||
);
|
||||
break;
|
||||
}
|
||||
let type_field = type;
|
||||
if (input.show_pass) {
|
||||
type_field = "text";
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
|
|
@ -341,7 +346,7 @@ export const TypeInput: React.FC<any> = ({
|
|||
required={required}
|
||||
placeholder={placeholder || ""}
|
||||
value={value}
|
||||
type={!type ? "text" : type}
|
||||
type={!type ? "text" : type_field}
|
||||
onChange={(ev) => {
|
||||
fm.data[name] = ev.currentTarget.value;
|
||||
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 TableRow from "@tiptap/extension-table-row";
|
||||
import { ButtonRichText } from "../../ui/button-rich-text";
|
||||
import { InitEditor } from "./AfterEditor";
|
||||
|
||||
export const TypeRichText: React.FC<any> = ({
|
||||
name,
|
||||
|
|
@ -822,7 +821,7 @@ export const TypeRichText: React.FC<any> = ({
|
|||
onChange(fm.data[name]);
|
||||
}
|
||||
}}
|
||||
content={input.value}
|
||||
content={fm.data?.[name]}
|
||||
editable={!disabled}
|
||||
></EditorProvider>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -199,7 +199,10 @@ export const FieldUploadSingle: FC<{
|
|||
</div>
|
||||
) : input.fase === "preview" ? (
|
||||
<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 ? (
|
||||
<>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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";
|
||||
interface TreeMenuItem {
|
||||
title: string;
|
||||
href?: string;
|
||||
|
|
@ -320,7 +321,7 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
|||
<Sidebar
|
||||
aria-label="Sidebar with multi-level dropdown example"
|
||||
className={classNames(
|
||||
"relative pt-0 sidebar rounded-none",
|
||||
"relative pt-0 rounded-none",
|
||||
mini ? "w-20" : "",
|
||||
css`
|
||||
> div {
|
||||
|
|
@ -332,20 +333,22 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
|||
`
|
||||
)}
|
||||
>
|
||||
<div className="w-full h-full relative ">
|
||||
<div className="flex h-full flex-col justify-between w-full absolute top-0 left-0">
|
||||
<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>
|
||||
<ScrollArea className="w-full h-full">
|
||||
<div className="w-full h-full relative ">
|
||||
<div className="flex h-full flex-col justify-between 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>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Sidebar>
|
||||
</div>
|
||||
<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";
|
||||
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 React, { useEffect, useState } from "react";
|
||||
import { Table } from "flowbite-react";
|
||||
import { HiChevronLeft, HiChevronRight } from "react-icons/hi";
|
||||
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 { toast } from "sonner";
|
||||
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 { formatMoney } from "../form/field/TypeInput";
|
||||
import { cloneFM } from "@/lib/utils/cloneFm";
|
||||
import { ResizableBox } from "react-resizable";
|
||||
export const TableEditBetter: React.FC<any> = ({
|
||||
name,
|
||||
column,
|
||||
|
|
@ -250,7 +230,14 @@ export const TableEditBetter: React.FC<any> = ({
|
|||
)}
|
||||
>
|
||||
{!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"}>
|
||||
{columns.map((col, idx) => {
|
||||
return (
|
||||
|
|
@ -265,7 +252,7 @@ export const TableEditBetter: React.FC<any> = ({
|
|||
}}
|
||||
>
|
||||
<div className="flex items-center h-full flex-grow p-2">
|
||||
<span>{col?.name}</span>
|
||||
<span>{col?.header()}</span>
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
|
|
@ -277,9 +264,25 @@ export const TableEditBetter: React.FC<any> = ({
|
|||
)}
|
||||
<tbody>
|
||||
{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 (
|
||||
<tr key={`row_${name}_${index}`}>
|
||||
<tr
|
||||
key={`row_${name}_${index}`}
|
||||
className={cx(css`
|
||||
td {
|
||||
vertical-align: ${align};
|
||||
}
|
||||
`)}
|
||||
>
|
||||
{columns.map((col, idx) => {
|
||||
const param = {
|
||||
row: row,
|
||||
|
|
@ -300,7 +303,7 @@ export const TableEditBetter: React.FC<any> = ({
|
|||
key={`row_${name}_${index}_${col?.accessorKey}_${idx}`}
|
||||
className={"table-header-tbl capitalize"}
|
||||
>
|
||||
{renderData}
|
||||
<div className="p-1">{renderData}</div>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -15,22 +15,21 @@ 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 { FaChevronUp } from "react-icons/fa6";
|
||||
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 { FaChevronDown } from "react-icons/fa";
|
||||
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 TableList: React.FC<any> = ({
|
||||
name,
|
||||
column,
|
||||
style = "UI",
|
||||
align = "center",
|
||||
onLoad,
|
||||
take = 20,
|
||||
|
|
@ -149,7 +148,18 @@ export const TableList: React.FC<any> = ({
|
|||
});
|
||||
const cloneListFM = (data: any[]) => {
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
|
@ -182,27 +192,38 @@ export const TableList: React.FC<any> = ({
|
|||
|
||||
local.render();
|
||||
}
|
||||
|
||||
if (Array.isArray(onLoad)) {
|
||||
local.data = onLoad;
|
||||
cloneListFM(onLoad);
|
||||
if (mode === "form") {
|
||||
local.data = fm.data?.[name] || [];
|
||||
cloneListFM(fm.data?.[name] || []);
|
||||
local.render();
|
||||
setData(onLoad);
|
||||
setData(fm.data?.[name] || []);
|
||||
} 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 (Array.isArray(onLoad)) {
|
||||
local.data = onLoad;
|
||||
cloneListFM(onLoad);
|
||||
local.render();
|
||||
setData(onLoad);
|
||||
} else if (typeof onLoad === "function") {
|
||||
const res: any = await onLoad({
|
||||
search: local.search,
|
||||
sort: local.sort,
|
||||
take,
|
||||
paging: 1,
|
||||
});
|
||||
local.data = res;
|
||||
cloneListFM(res);
|
||||
local.render();
|
||||
setData(res);
|
||||
} else {
|
||||
local.data = onLoad;
|
||||
cloneListFM(onLoad);
|
||||
local.render();
|
||||
setData(onLoad);
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
toast.dismiss();
|
||||
}, 2000);
|
||||
};
|
||||
if (typeof onInit === "function") {
|
||||
onInit(local);
|
||||
|
|
@ -337,7 +358,7 @@ export const TableList: React.FC<any> = ({
|
|||
<>
|
||||
<div className="tbl-wrapper flex flex-grow flex-col">
|
||||
{!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="sm:flex flex flex-col space-y-2">
|
||||
<div className="flex">
|
||||
|
|
@ -399,28 +420,28 @@ export const TableList: React.FC<any> = ({
|
|||
<div className="relative">
|
||||
<Table
|
||||
className={cx(
|
||||
"min-w-full divide-y divide-gray-200 ",
|
||||
"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;
|
||||
}
|
||||
// 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`
|
||||
|
|
@ -440,7 +461,14 @@ export const TableList: React.FC<any> = ({
|
|||
)}
|
||||
>
|
||||
{!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) => (
|
||||
<tr
|
||||
key={`${headerGroup.id}`}
|
||||
|
|
@ -536,29 +564,38 @@ export const TableList: React.FC<any> = ({
|
|||
</div>
|
||||
{isSort ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<FaChevronUp
|
||||
className={cx(
|
||||
"px-0.5 mx-1 text-[12px]",
|
||||
local?.sort?.[name] === "asc"
|
||||
? "text-black"
|
||||
: "text-gray-500"
|
||||
)}
|
||||
/>
|
||||
<FaChevronDown
|
||||
className={cx(
|
||||
"px-0.5 mx-1 text-[12px]",
|
||||
local?.sort?.[name] === "desc"
|
||||
? "text-black"
|
||||
: "text-gray-500"
|
||||
)}
|
||||
/>
|
||||
{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>
|
||||
|
||||
{headerGroup.headers.length !== index + 1 ? (
|
||||
{resize &&
|
||||
headerGroup.headers.length !== index + 1 ? (
|
||||
<div
|
||||
key={`${header.id}-resizer`} // Tambahkan key unik
|
||||
{...{
|
||||
|
|
@ -567,15 +604,15 @@ export const TableList: React.FC<any> = ({
|
|||
onMouseDown: header.getResizeHandler(),
|
||||
onTouchStart: header.getResizeHandler(),
|
||||
className: cx(
|
||||
`resizer bg-[#b3c9fe] cursor-e-resize ${
|
||||
`resizer hover:bg-second cursor-e-resize ${
|
||||
table.options.columnResizeDirection
|
||||
} ${
|
||||
header.column.getIsResizing()
|
||||
? "isResizing"
|
||||
: ""
|
||||
? "isResizing bg-second"
|
||||
: " bg-primary"
|
||||
}`,
|
||||
css`
|
||||
width: 1px;
|
||||
width: 5px;
|
||||
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) => {
|
||||
const fm_row =
|
||||
mode === "form" ? local.dataForm?.[idx] : null;
|
||||
|
|
@ -623,7 +660,8 @@ export const TableList: React.FC<any> = ({
|
|||
vertical-align: ${align};
|
||||
}
|
||||
`,
|
||||
"border-none"
|
||||
"border-none ",
|
||||
style === "UI" ? "even:bg-linear-blue " : ""
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell: any) => {
|
||||
|
|
@ -737,7 +775,7 @@ export const Pagination: React.FC<any> = ({
|
|||
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 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">
|
||||
Showing {local.page * 20 - 19} to{" "}
|
||||
{list.data?.length >= 20
|
||||
|
|
@ -843,7 +881,7 @@ export const PaginationPage: React.FC<any> = ({
|
|||
local.render();
|
||||
}, [page, count]);
|
||||
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 items-center flex-row gap-x-2 sm:mb-0 text-sm">
|
||||
<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-checkbox": "^1.1.3",
|
||||
"@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-tabs": "^1.1.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.7",
|
||||
"@react-pdf/renderer": "^4.1.5",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
"@tiptap/extension-color": "^2.11.2",
|
||||
|
|
|
|||
Loading…
Reference in New Issue