banyak bos
This commit is contained in:
parent
bb0e6d38f1
commit
31680f29c7
|
|
@ -46,7 +46,9 @@ export function usePopover({
|
||||||
const arrowRef = React.useRef<HTMLDivElement | null>(null);
|
const arrowRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen);
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen);
|
||||||
const [labelId, setLabelId] = React.useState<string | undefined>();
|
const [labelId, setLabelId] = React.useState<string | undefined>();
|
||||||
const [descriptionId, setDescriptionId] = React.useState<string | undefined>();
|
const [descriptionId, setDescriptionId] = React.useState<
|
||||||
|
string | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
// Determine whether the popover is open
|
// Determine whether the popover is open
|
||||||
const open = controlledOpen ?? uncontrolledOpen;
|
const open = controlledOpen ?? uncontrolledOpen;
|
||||||
|
|
@ -113,7 +115,6 @@ export function usePopover({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function mapPlacementSideToCSSProperty(placement: Placement) {
|
function mapPlacementSideToCSSProperty(placement: Placement) {
|
||||||
const staticPosition = placement.split("-")[0];
|
const staticPosition = placement.split("-")[0];
|
||||||
|
|
||||||
|
|
@ -186,6 +187,7 @@ export function Popover({
|
||||||
className,
|
className,
|
||||||
classNameTrigger,
|
classNameTrigger,
|
||||||
arrow,
|
arrow,
|
||||||
|
popoverClassName,
|
||||||
...restOptions
|
...restOptions
|
||||||
}: {
|
}: {
|
||||||
root?: HTMLElement;
|
root?: HTMLElement;
|
||||||
|
|
@ -193,6 +195,7 @@ export function Popover({
|
||||||
classNameTrigger?: string;
|
classNameTrigger?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
content?: React.ReactNode;
|
content?: React.ReactNode;
|
||||||
|
popoverClassName?: string;
|
||||||
arrow?: boolean;
|
arrow?: boolean;
|
||||||
} & PopoverOptions) {
|
} & PopoverOptions) {
|
||||||
const popover = usePopover({ modal, ...restOptions });
|
const popover = usePopover({ modal, ...restOptions });
|
||||||
|
|
@ -216,14 +219,17 @@ export function Popover({
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className={cx(
|
className={cx(
|
||||||
className,
|
popoverClassName
|
||||||
css`
|
? popoverClassName
|
||||||
background: white;
|
: cx(
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
|
className,
|
||||||
user-select: none;
|
css`
|
||||||
`,
|
background: white;
|
||||||
|
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
user-select: none;
|
||||||
|
`
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
>
|
>
|
||||||
{_content}
|
{_content}
|
||||||
{(typeof arrow === "undefined" || arrow) && <PopoverArrow />}
|
{(typeof arrow === "undefined" || arrow) && <PopoverArrow />}
|
||||||
|
|
@ -269,7 +275,6 @@ export const PopoverTrigger = React.forwardRef<
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export const PopoverContent = React.forwardRef<
|
export const PopoverContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLProps<HTMLDivElement>
|
React.HTMLProps<HTMLDivElement>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { FieldCheckbox } from "./field/TypeCheckbox";
|
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 { FieldUploadMulti } from "./field/TypeUploadMulti";
|
||||||
|
import { TypeRichText } from "./field/TypeRichText";
|
||||||
|
import { TypeTag } from "./field/TypeTag";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
|
|
||||||
export const Field: React.FC<any> = ({
|
export const Field: React.FC<any> = ({
|
||||||
fm,
|
fm,
|
||||||
|
|
@ -18,8 +22,13 @@ export const Field: React.FC<any> = ({
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
|
prefix,
|
||||||
|
suffix,
|
||||||
}) => {
|
}) => {
|
||||||
let result = null;
|
let result = null;
|
||||||
|
|
||||||
|
const suffixRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const prefixRef = useRef<HTMLDivElement | null>(null);
|
||||||
const is_disable = fm.mode === "view" ? true : disabled;
|
const is_disable = fm.mode === "view" ? true : disabled;
|
||||||
const error = fm.error?.[name];
|
const error = fm.error?.[name];
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -42,6 +51,8 @@ export const Field: React.FC<any> = ({
|
||||||
fm.render();
|
fm.render();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
const before = typeof prefix === "function" ? prefix() : prefix;
|
||||||
|
const after = typeof suffix === "function" ? suffix() : suffix;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
@ -65,11 +76,28 @@ export const Field: React.FC<any> = ({
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
error
|
error
|
||||||
? "flex flex-row rounded-md flex-grow border-red-500 border"
|
? "flex flex-row rounded-md flex-grow border-red-500 border items-center"
|
||||||
: "flex flex-row rounded-md flex-grow",
|
: "flex flex-row rounded-md flex-grow items-center",
|
||||||
is_disable ? "bg-gray-100" : ""
|
is_disable ? "bg-gray-100" : "",
|
||||||
|
"relative"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{before && (
|
||||||
|
<div
|
||||||
|
ref={prefixRef}
|
||||||
|
className={cx(
|
||||||
|
"absolute left-[1px] px-1 py-1 bg-gray-200/50 border border-gray-100 items-center flex flex-row flex-grow rounded-l-md",
|
||||||
|
css`
|
||||||
|
height: 2.13rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
`,
|
||||||
|
is_disable ? "bg-gray-100" : "bg-gray-200/50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{before}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{["upload"].includes(type) ? (
|
{["upload"].includes(type) ? (
|
||||||
<>
|
<>
|
||||||
<TypeUpload
|
<TypeUpload
|
||||||
|
|
@ -139,6 +167,24 @@ export const Field: React.FC<any> = ({
|
||||||
mode="single"
|
mode="single"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
) : ["richtext"].includes(type) ? (
|
||||||
|
<>
|
||||||
|
<TypeRichText
|
||||||
|
fm={fm}
|
||||||
|
name={name}
|
||||||
|
disabled={is_disable}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : ["tag"].includes(type) ? (
|
||||||
|
<>
|
||||||
|
<TypeTag
|
||||||
|
fm={fm}
|
||||||
|
name={name}
|
||||||
|
disabled={is_disable}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<TypeInput
|
<TypeInput
|
||||||
|
|
@ -149,9 +195,41 @@ export const Field: React.FC<any> = ({
|
||||||
type={type}
|
type={type}
|
||||||
disabled={is_disable}
|
disabled={is_disable}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
className={cx(
|
||||||
|
before &&
|
||||||
|
css`
|
||||||
|
padding-left: ${getNumber(
|
||||||
|
get(prefixRef, "current.clientWidth")
|
||||||
|
) + 10}px;
|
||||||
|
`,
|
||||||
|
after &&
|
||||||
|
css`
|
||||||
|
padding-right: ${getNumber(
|
||||||
|
get(suffixRef, "current.clientWidth")
|
||||||
|
) + 10}px;
|
||||||
|
`
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{after && (
|
||||||
|
<div
|
||||||
|
ref={suffixRef}
|
||||||
|
className={cx(
|
||||||
|
"absolute right-[1px] px-1 py-1 items-center flex flex-row flex-grow rounded-r-md",
|
||||||
|
css`
|
||||||
|
height: 2.13rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
`,
|
||||||
|
is_disable
|
||||||
|
? "bg-gray-200/50 border-l border-gray-300"
|
||||||
|
: "bg-gray-200/50 border border-gray-100"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{after}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="text-sm text-red-500 py-1">{error}</div>
|
<div className="text-sm text-red-500 py-1">{error}</div>
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,16 @@ export const FormBetter: React.FC<any> = ({
|
||||||
});
|
});
|
||||||
useEffect(() => {}, [fm.data]);
|
useEffect(() => {}, [fm.data]);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-grow">
|
<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 w-full">{onTitle(fm)}</div>
|
<div className="flex flex-row p-3 items-center bg-white border border-gray-300 rounded-lg">
|
||||||
|
{onTitle(fm)}
|
||||||
|
</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 overflow-hidden">
|
||||||
<div className="w-full flex flex-row flex-grow bg-white rounded-lg relative overflow-y-scroll shadow">
|
<div className="w-full flex flex-row flex-grow bg-white rounded-lg border border-gray-300 relative overflow-y-scroll">
|
||||||
<Form
|
<Form
|
||||||
{...{
|
{...{
|
||||||
children,
|
children,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import Datepicker from "../../ui/Datepicker";
|
||||||
|
import { Input } from "../../ui/input";
|
||||||
|
import { Textarea } from "../../ui/text-area";
|
||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import tinycolor from "tinycolor2";
|
||||||
|
import { HexColorPicker } from "react-colorful";
|
||||||
|
|
||||||
|
export const TypeColor: React.FC<any> = ({
|
||||||
|
value,
|
||||||
|
onChangePicker,
|
||||||
|
onClose,
|
||||||
|
}) => {
|
||||||
|
const meta = useLocal({
|
||||||
|
originalValue: "",
|
||||||
|
inputValue: value,
|
||||||
|
rgbValue: "",
|
||||||
|
selectedEd: "" as string,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
meta.inputValue = value || "";
|
||||||
|
const convertColor = tinycolor(meta.inputValue);
|
||||||
|
meta.rgbValue = convertColor.toRgbString();
|
||||||
|
meta.render();
|
||||||
|
}, [value]);
|
||||||
|
const tin = tinycolor(meta.inputValue);
|
||||||
|
return (
|
||||||
|
<div className="flex p-3 space-x-4 items-start">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-col items-center",
|
||||||
|
css`
|
||||||
|
.react-colorful__pointer {
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Suspense>
|
||||||
|
<HexColorPicker
|
||||||
|
color={meta.inputValue}
|
||||||
|
onChange={(color) => {
|
||||||
|
if (color) {
|
||||||
|
meta.inputValue = color;
|
||||||
|
onChangePicker(color);
|
||||||
|
const convertColor = tinycolor(meta.inputValue);
|
||||||
|
meta.rgbValue = convertColor.toRgbString();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"grid grid-cols-1 gap-y-0.5",
|
||||||
|
css`
|
||||||
|
width: 78px;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="p-[1px] border rounded flex items-center justify-center"
|
||||||
|
style={{
|
||||||
|
marginBottom: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
value={meta.inputValue || "#FFFFFFFF"}
|
||||||
|
className={cx(
|
||||||
|
`rounded cursor-text bg-[${meta.inputValue}] min-w-[0px] text-[13px] px-[8px] py-[1px] uppercase`,
|
||||||
|
tin.isValid() &&
|
||||||
|
css`
|
||||||
|
color: ${!tin.isLight() ? "#FFF" : "#000"};
|
||||||
|
background-color: ${meta.inputValue || ""};
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
spellCheck={false}
|
||||||
|
onChange={(e) => {
|
||||||
|
const color = e.currentTarget.value;
|
||||||
|
meta.inputValue = color;
|
||||||
|
onChangePicker(color);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="">
|
||||||
|
{meta.inputValue !== "" && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="cursor-pointer text-center border border-gray-200 rounded hover:bg-gray-100"
|
||||||
|
onClick={() => {
|
||||||
|
meta.inputValue = "";
|
||||||
|
onChangePicker("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{onClose && (
|
||||||
|
<div
|
||||||
|
className="cursor-pointer text-center border border-gray-200 rounded hover:bg-gray-100 mt-[4px]"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -3,6 +3,8 @@ import Datepicker from "../../ui/Datepicker";
|
||||||
import { Input } from "../../ui/input";
|
import { Input } from "../../ui/input";
|
||||||
import { Textarea } from "../../ui/text-area";
|
import { Textarea } from "../../ui/text-area";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import tinycolor from "tinycolor2";
|
||||||
|
import { FieldColorPicker } from "../../ui/FieldColorPopover";
|
||||||
|
|
||||||
export const TypeInput: React.FC<any> = ({
|
export const TypeInput: React.FC<any> = ({
|
||||||
name,
|
name,
|
||||||
|
|
@ -13,12 +15,28 @@ export const TypeInput: React.FC<any> = ({
|
||||||
type,
|
type,
|
||||||
field,
|
field,
|
||||||
onChange,
|
onChange,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
let value: any = fm.data?.[name] || "";
|
let value: any = fm.data?.[name] || "";
|
||||||
const input = useLocal({
|
const input = useLocal({
|
||||||
value: 0 as any,
|
value: 0 as any,
|
||||||
ref: null as any,
|
ref: null as any,
|
||||||
|
open: false,
|
||||||
});
|
});
|
||||||
|
const meta = useLocal({
|
||||||
|
originalValue: "",
|
||||||
|
inputValue: value,
|
||||||
|
rgbValue: "",
|
||||||
|
selectedEd: "" as string,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (type === "color") {
|
||||||
|
meta.inputValue = value || "";
|
||||||
|
const convertColor = tinycolor(meta.inputValue);
|
||||||
|
meta.rgbValue = convertColor.toRgbString();
|
||||||
|
meta.render();
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type === "money") {
|
if (type === "money") {
|
||||||
input.value =
|
input.value =
|
||||||
|
|
@ -67,6 +85,51 @@ export const TypeInput: React.FC<any> = ({
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "color":
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<div className="border border-gray-300 p-0.5 rounded-sm">
|
||||||
|
<FieldColorPicker
|
||||||
|
value={fm.data?.[name]}
|
||||||
|
update={(val) => {
|
||||||
|
fm.data[name] = val;
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
onOpen={() => {
|
||||||
|
input.open = true;
|
||||||
|
input.render();
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
input.open = false;
|
||||||
|
input.render();
|
||||||
|
}}
|
||||||
|
open={input.open}
|
||||||
|
showHistory={false}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><path d="M8 0h8v8H8zM0 8h8v8H0z"/></svg>');
|
||||||
|
`,
|
||||||
|
"cursor-pointer rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"rounded-sm h-8 w-8",
|
||||||
|
css`
|
||||||
|
background: ${fm?.data?.[name]};
|
||||||
|
`,
|
||||||
|
"color-box"
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</FieldColorPicker>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
case "date":
|
case "date":
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -111,7 +174,8 @@ export const TypeInput: React.FC<any> = ({
|
||||||
? "rgb(243 244 246)"
|
? "rgb(243 244 246)"
|
||||||
: "transparant"}
|
: "transparant"}
|
||||||
? "";
|
? "";
|
||||||
`
|
`,
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
required={required}
|
required={required}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
|
|
@ -165,7 +229,8 @@ export const TypeInput: React.FC<any> = ({
|
||||||
css`
|
css`
|
||||||
background-color: ${disabled ? "rgb(243 244 246)" : "transparant"} ?
|
background-color: ${disabled ? "rgb(243 244 246)" : "transparant"} ?
|
||||||
"";
|
"";
|
||||||
`
|
`,
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
required={required}
|
required={required}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,821 @@
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import { Input } from "../../ui/input";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
useEditor,
|
||||||
|
EditorContent,
|
||||||
|
useCurrentEditor,
|
||||||
|
EditorProvider,
|
||||||
|
} from "@tiptap/react";
|
||||||
|
import Underline from "@tiptap/extension-underline";
|
||||||
|
import Link from "@tiptap/extension-link";
|
||||||
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
|
import { Color } from "@tiptap/extension-color";
|
||||||
|
import ListItem from "@tiptap/extension-list-item";
|
||||||
|
import TextAlign from "@tiptap/extension-text-align";
|
||||||
|
import TextStyle from "@tiptap/extension-text-style";
|
||||||
|
import { Popover } from "../../Popover/Popover";
|
||||||
|
import { ButtonBetter } from "../../ui/button";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
|
||||||
|
import Table from "@tiptap/extension-table";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export const TypeRichText: React.FC<any> = ({
|
||||||
|
name,
|
||||||
|
fm,
|
||||||
|
placeholder,
|
||||||
|
disabled = false,
|
||||||
|
required,
|
||||||
|
type,
|
||||||
|
field,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
let value: any = fm.data?.[name] || "";
|
||||||
|
const input = useLocal({
|
||||||
|
value: 0 as any,
|
||||||
|
ref: null as any,
|
||||||
|
open: false,
|
||||||
|
});
|
||||||
|
const [url, setUrl] = useState(null as any);
|
||||||
|
const local = useLocal({
|
||||||
|
open: false,
|
||||||
|
data: ["General", "Table"],
|
||||||
|
tab: 0,
|
||||||
|
active: "General",
|
||||||
|
});
|
||||||
|
useEffect(() => {}, [fm.data?.[name]]);
|
||||||
|
|
||||||
|
useEffect(() => {}, []);
|
||||||
|
const MenuBar = () => {
|
||||||
|
const { editor } = useCurrentEditor();
|
||||||
|
if (disabled) return <></>;
|
||||||
|
if (!editor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"control-group sticky top-0 bg-white shadow rounded-t-lg",
|
||||||
|
css`
|
||||||
|
z-index: 2;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
className="flex flex-col w-full pt-1 bg-gray-100 rounded-t-lg"
|
||||||
|
defaultValue={local.active}
|
||||||
|
value={local.active}
|
||||||
|
>
|
||||||
|
<TabsList className="flex flex-row relative w-full p-0 rounded-none rounded-t-md justify-start">
|
||||||
|
{local.data.map((e, idx) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-row items-center relative"
|
||||||
|
key={`container_tab_${e}_${idx}`}
|
||||||
|
>
|
||||||
|
<TabsTrigger
|
||||||
|
value={e}
|
||||||
|
onClick={() => {
|
||||||
|
local.tab = idx;
|
||||||
|
local.active = e;
|
||||||
|
local.render();
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
"p-1.5 px-4 border text-sm font-normal data-[state=active]:font-bold data-[state=active]:bg-white border-none border-r bg-transparent data-[state=active]:text-primary data-[state=active]:shadow-none data-[state=active]:border-none",
|
||||||
|
!idx
|
||||||
|
? "ml-1.5"
|
||||||
|
: idx++ === local.data.length
|
||||||
|
? "mr-2"
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
key={e}
|
||||||
|
>
|
||||||
|
{e}
|
||||||
|
</TabsTrigger>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"w-0.5 h-4 bg-white rounded-full absolute right-[-1px]",
|
||||||
|
css`
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
`,
|
||||||
|
(idx === local.tab || idx + 1 === local.tab) && "hidden"
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value={"General"} className="bg-gray-100">
|
||||||
|
<div className="button-group flex flex-row gap-x-2 p-2 rounded-t-lg bg-white">
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleBold().run()}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("bold") ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md px-2"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 2h4.5a3.501 3.501 0 0 1 2.852 5.53A3.499 3.499 0 0 1 9.5 14H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1m1 7v3h4.5a1.5 1.5 0 0 0 0-3Zm3.5-2a1.5 1.5 0 0 0 0-3H5v3Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("italic") ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M10 6h4m4 0h-4m0 0l-4 12m0 0h4m-4 0H6"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("underline") ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M7 5v5a5 5 0 0 0 10 0V5M5 19h14"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
disabled={!editor.can().chain().focus().toggleStrike().run()}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("strike") ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M17.154 14q.346.774.346 1.72q0 2.014-1.571 3.147Q14.357 20 11.586 20q-2.46 0-4.87-1.145v-2.254q2.28 1.316 4.666 1.316q3.826 0 3.839-2.197a2.2 2.2 0 0 0-.648-1.603l-.12-.117H3v-2h18v2zm-4.078-3H7.629a4 4 0 0 1-.481-.522Q6.5 9.643 6.5 8.452q0-1.854 1.397-3.153T12.222 4q2.207 0 4.222.984v2.152q-1.8-1.03-3.946-1.03q-3.72 0-3.719 2.346q0 .63.654 1.099q.654.47 1.613.75q.93.27 2.03.699"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("bulletList") ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M1.5 5.25a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5M4 4.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5M4.5 7a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1zm0 3a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1zM2.25 7.5a.75.75 0 1 1-1.5 0a.75.75 0 0 1 1.5 0m-.75 3.75a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("orderedList") ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="none">
|
||||||
|
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M5.436 16.72a1.466 1.466 0 0 1 1.22 2.275a1.466 1.466 0 0 1-1.22 2.275c-.65 0-1.163-.278-1.427-.901a.65.65 0 1 1 1.196-.508a.18.18 0 0 0 .165.109c.109 0 .23-.03.23-.167c0-.1-.073-.143-.156-.154l-.051-.004a.65.65 0 0 1-.096-1.293l.096-.007c.102 0 .207-.037.207-.158c0-.137-.12-.167-.23-.167a.18.18 0 0 0-.164.11a.65.65 0 1 1-1.197-.509c.264-.622.777-.9 1.427-.9ZM20 18a1 1 0 1 1 0 2H9a1 1 0 1 1 0-2zM6.08 9.945a1.552 1.552 0 0 1 .43 2.442l-.554.593h.47a.65.65 0 1 1 0 1.3H4.573a.655.655 0 0 1-.655-.654c0-.207.029-.399.177-.557L5.559 11.5c.11-.117.082-.321-.06-.392c-.136-.068-.249.01-.275.142l-.006.059a.65.65 0 0 1-.65.65c-.39 0-.65-.327-.65-.697a1.482 1.482 0 0 1 2.163-1.317ZM20 11a1 1 0 0 1 .117 1.993L20 13H9a1 1 0 0 1-.117-1.993L9 11zM6.15 3.39v3.24a.65.65 0 1 1-1.3 0V4.522a.65.65 0 0 1-.46-1.183l.742-.495a.655.655 0 0 1 1.018.545ZM20 4a1 1 0 0 1 .117 1.993L20 6H9a1 1 0 0 1-.117-1.993L9 4z"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().setTextAlign("left").run()
|
||||||
|
}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive({ textAlign: "left" })
|
||||||
|
? "is-active bg-gray-200"
|
||||||
|
: "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 6h16M4 12h10M4 18h14"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().setTextAlign("center").run()
|
||||||
|
}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive({ textAlign: "center" })
|
||||||
|
? "is-active bg-gray-200"
|
||||||
|
: "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 6h16M8 12h8M6 18h12"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().setTextAlign("right").run()
|
||||||
|
}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive({ textAlign: "right" })
|
||||||
|
? "is-active bg-gray-200"
|
||||||
|
: "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 6h16m-10 6h10M6 18h14"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().setTextAlign("justify").run()
|
||||||
|
}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive({ textAlign: "justify" })
|
||||||
|
? "is-active bg-gray-200"
|
||||||
|
: "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 6h16M4 12h16M4 18h12"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
||||||
|
}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("heading", { level: 1 })
|
||||||
|
? "is-active bg-gray-200"
|
||||||
|
: "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M14 4.25a.75.75 0 0 0-1.248-.56l-2.25 2a.75.75 0 0 0 .996 1.12l1.002-.89v5.83a.75.75 0 0 0 1.5 0zm-11.5 0a.75.75 0 0 0-1.5 0v7.496a.75.75 0 0 0 1.5 0V8.75h4v2.996a.75.75 0 0 0 1.5 0V4.25a.75.75 0 0 0-1.5 0v3h-4z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||||
|
}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("heading", { level: 2 })
|
||||||
|
? "is-active bg-gray-200"
|
||||||
|
: "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M2.5 4.25a.75.75 0 0 0-1.5 0v7.496a.75.75 0 0 0 1.5 0V8.75h4v2.996a.75.75 0 0 0 1.5 0V4.25a.75.75 0 0 0-1.5 0v3h-4zm8.403 1.783A1.364 1.364 0 0 1 12.226 5h.226a1.071 1.071 0 0 1 .672 1.906l-3.61 2.906a1.51 1.51 0 0 0 .947 2.688h3.789a.75.75 0 0 0 0-1.5h-3.793l-.003-.003l-.003-.004v-.004a.01.01 0 0 1 .004-.008l3.61-2.907A2.571 2.571 0 0 0 12.452 3.5h-.226c-1.314 0-2.46.894-2.778 2.17l-.038.148a.75.75 0 1 0 1.456.364z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||||
|
}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("heading", { level: 3 })
|
||||||
|
? "is-active bg-gray-200"
|
||||||
|
: "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M2.5 4.25a.75.75 0 0 0-1.5 0v7.496a.75.75 0 0 0 1.5 0V8.75h4v2.996a.75.75 0 0 0 1.5 0V4.25a.75.75 0 0 0-1.5 0v3h-4zm8.114 1.496c.202-.504.69-.834 1.232-.834h.28a.94.94 0 0 1 .929.796l.027.18a1.15 1.15 0 0 1-.911 1.303l-.8.16a.662.662 0 0 0 .129 1.31h1.21a.89.89 0 0 1 .882 1.017a1.67 1.67 0 0 1-1.414 1.414l-.103.015a1.81 1.81 0 0 1-1.828-.9l-.018-.033a.662.662 0 0 0-1.152.652l.018.032a3.13 3.13 0 0 0 3.167 1.559l.103-.015a2.99 2.99 0 0 0 2.537-2.537a2.21 2.21 0 0 0-1.058-2.216a2.47 2.47 0 0 0 .547-1.963l-.028-.179a2.26 2.26 0 0 0-2.237-1.919h-.28a2.65 2.65 0 0 0-2.46 1.666a.662.662 0 1 0 1.228.492"
|
||||||
|
clipRule="evenodd"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Popover
|
||||||
|
classNameTrigger={""}
|
||||||
|
arrow={false}
|
||||||
|
className="rounded-md"
|
||||||
|
onOpenChange={(open: any) => {
|
||||||
|
if (!editor.isActive("link")) {
|
||||||
|
local.open = open;
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
open={local.open}
|
||||||
|
content={
|
||||||
|
<div className="flex flex-row px-2 py-4 gap-y-2 items-center">
|
||||||
|
<Input
|
||||||
|
id="maxWidth"
|
||||||
|
value={url || ""}
|
||||||
|
className="col-span-2 h-9"
|
||||||
|
onChange={(e) => {
|
||||||
|
setUrl(get(e, "currentTarget.value"));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row justify-end">
|
||||||
|
<ButtonBetter
|
||||||
|
onClick={() => {
|
||||||
|
if (url) {
|
||||||
|
try {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.extendMarkRange("link")
|
||||||
|
.setLink({ href: url })
|
||||||
|
.run();
|
||||||
|
local.open = false;
|
||||||
|
local.render();
|
||||||
|
setUrl("");
|
||||||
|
} catch (e: any) {
|
||||||
|
alert(e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</ButtonBetter>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
editor.chain().focus().unsetLink().run();
|
||||||
|
}}
|
||||||
|
className={cx(
|
||||||
|
editor.isActive("link") ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{editor.isActive("link") ? (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m15.221 12.41l-.91-.91h.65q.214 0 .357.143t.144.357q0 .135-.062.239q-.061.103-.179.17m5.625 9.145q-.16.16-.354.16t-.353-.16L2.445 3.862q-.14-.14-.15-.345t.15-.363t.354-.16t.354.16l17.692 17.692q.14.14.15.345q.01.203-.15.363M7.077 16.077q-1.69 0-2.884-1.193T3 12q0-1.61 1.098-2.777t2.69-1.265h.462l.966.965H7.077q-1.27 0-2.173.904Q4 10.731 4 12t.904 2.173t2.173.904h3.077q.213 0 .357.143t.143.357t-.143.357t-.357.143zM9.039 12.5q-.214 0-.357-.143T8.539 12t.143-.357t.357-.143h1.759l.975 1zm9.134 2.916q-.11-.178-.057-.385t.25-.298q.748-.387 1.19-1.118Q20 12.885 20 12q0-1.27-.894-2.173q-.895-.904-2.145-.904h-3.115q-.213 0-.356-.143t-.144-.357t.144-.357t.356-.143h3.116q1.67 0 2.854 1.193T21 12q0 1.148-.591 2.095q-.592.947-1.553 1.493q-.177.11-.375.057t-.308-.23"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M7.077 16.077q-1.692 0-2.884-1.192T3 12t1.193-2.885t2.884-1.193h3.039q.212 0 .356.144t.144.357t-.144.356t-.356.143H7.075q-1.267 0-2.171.904T4 12t.904 2.173t2.17.904h3.042q.212 0 .356.144t.144.357t-.144.356t-.356.143zM9 12.5q-.213 0-.356-.144t-.144-.357t.144-.356T9 11.5h6q.213 0 .356.144t.144.357t-.144.356T15 12.5zm4.885 3.577q-.213 0-.357-.144t-.144-.357t.144-.356t.356-.143h3.041q1.267 0 2.171-.904T20 12t-.904-2.173t-2.17-.904h-3.041q-.213 0-.357-.144q-.143-.144-.143-.357t.143-.356t.357-.143h3.038q1.692 0 2.885 1.192T21 12t-1.193 2.885t-2.884 1.193z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value={"Table"} className="bg-gray-100">
|
||||||
|
<div className="button-group flex flex-row gap-x-2 p-2 rounded-t-lg bg-white">
|
||||||
|
<ButtonRichText
|
||||||
|
onClick={(e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
|
||||||
|
.run();
|
||||||
|
}}
|
||||||
|
disabled={false}
|
||||||
|
active={false}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
d="M9 3.5v17m11.5-11h-17M3 9.4c0-2.24 0-3.36.436-4.216a4 4 0 0 1 1.748-1.748C6.04 3 7.16 3 9.4 3h5.2c2.24 0 3.36 0 4.216.436a4 4 0 0 1 1.748 1.748C21 6.04 21 7.16 21 9.4v5.2c0 2.24 0 3.36-.436 4.216a4 4 0 0 1-1.748 1.748C17.96 21 16.84 21 14.6 21H9.4c-2.24 0-3.36 0-4.216-.436a4 4 0 0 1-1.748-1.748C3 17.96 3 16.84 3 14.6z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</ButtonRichText>
|
||||||
|
<ButtonRichText
|
||||||
|
onClick={(e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
editor.commands.setCellAttribute(
|
||||||
|
"className",
|
||||||
|
"tiptap-border-none"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
disabled={false}
|
||||||
|
active={false}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12 9q-.425 0-.712-.288T11 8t.288-.712T12 7t.713.288T13 8t-.288.713T12 9m-4 4q-.425 0-.712-.288T7 12t.288-.712T8 11t.713.288T9 12t-.288.713T8 13m4 0q-.425 0-.712-.288T11 12t.288-.712T12 11t.713.288T13 12t-.288.713T12 13m4 0q-.425 0-.712-.288T15 12t.288-.712T16 11t.713.288T17 12t-.288.713T16 13m-4 4q-.425 0-.712-.288T11 16t.288-.712T12 15t.713.288T13 16t-.288.713T12 17M4 5q-.425 0-.712-.288T3 4t.288-.712T4 3t.713.288T5 4t-.288.713T4 5m4 0q-.425 0-.712-.288T7 4t.288-.712T8 3t.713.288T9 4t-.288.713T8 5m4 0q-.425 0-.712-.288T11 4t.288-.712T12 3t.713.288T13 4t-.288.713T12 5m4 0q-.425 0-.712-.288T15 4t.288-.712T16 3t.713.288T17 4t-.288.713T16 5m4 0q-.425 0-.712-.288T19 4t.288-.712T20 3t.713.288T21 4t-.288.713T20 5M4 9q-.425 0-.712-.288T3 8t.288-.712T4 7t.713.288T5 8t-.288.713T4 9m16 0q-.425 0-.712-.288T19 8t.288-.712T20 7t.713.288T21 8t-.288.713T20 9M4 13q-.425 0-.712-.288T3 12t.288-.712T4 11t.713.288T5 12t-.288.713T4 13m16 0q-.425 0-.712-.288T19 12t.288-.712T20 11t.713.288T21 12t-.288.713T20 13M4 17q-.425 0-.712-.288T3 16t.288-.712T4 15t.713.288T5 16t-.288.713T4 17m16 0q-.425 0-.712-.288T19 16t.288-.712T20 15t.713.288T21 16t-.288.713T20 17M4 21q-.425 0-.712-.288T3 20t.288-.712T4 19t.713.288T5 20t-.288.713T4 21m4 0q-.425 0-.712-.288T7 20t.288-.712T8 19t.713.288T9 20t-.288.713T8 21m4 0q-.425 0-.712-.288T11 20t.288-.712T12 19t.713.288T13 20t-.288.713T12 21m4 0q-.425 0-.712-.288T15 20t.288-.712T16 19t.713.288T17 20t-.288.713T16 21m4 0q-.425 0-.712-.288T19 20t.288-.712T20 19t.713.288T21 20t-.288.713T20 21"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</ButtonRichText>
|
||||||
|
|
||||||
|
<ButtonRichText
|
||||||
|
onClick={(e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
editor.commands.setCellAttribute("className", "");
|
||||||
|
}}
|
||||||
|
disabled={false}
|
||||||
|
active={false}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M3 5v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2m8 14H6c-.55 0-1-.45-1-1v-5h5c.55 0 1 .45 1 1zm-1-8H5V6c0-.55.45-1 1-1h5v5c0 .55-.45 1-1 1m8 8h-5v-5c0-.55.45-1 1-1h5v5c0 .55-.45 1-1 1m1-8h-5c-.55 0-1-.45-1-1V5h5c.55 0 1 .45 1 1z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</ButtonRichText>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const CustomTable = Table.extend({
|
||||||
|
resizable: true,
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
class: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const extensions = [
|
||||||
|
Color.configure({ types: [TextStyle.name, ListItem.name] }),
|
||||||
|
Table.configure({
|
||||||
|
resizable: true,
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: "my-custom-class", // Tambahkan kelas default
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
TableRow,
|
||||||
|
TableHeader.extend({
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
className: {
|
||||||
|
default: null, // Nilai default tidak ada
|
||||||
|
parseHTML: (element) => element.getAttribute("class") || null, // Ambil class dari elemen
|
||||||
|
renderHTML: (attributes) => {
|
||||||
|
if (!attributes.className) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
class: attributes.className, // Tambahkan class ke HTML output
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
default: null, // Nilai default
|
||||||
|
parseHTML: (element) => element.style.backgroundColor || null,
|
||||||
|
renderHTML: (attributes) => {
|
||||||
|
if (!attributes.backgroundColor) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
style: `background-color: ${attributes.backgroundColor};`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
TableCell.extend({
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
className: {
|
||||||
|
default: null, // Nilai default tidak ada
|
||||||
|
parseHTML: (element) => element.getAttribute("class") || null, // Ambil class dari elemen
|
||||||
|
renderHTML: (attributes) => {
|
||||||
|
if (!attributes.className) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
class: attributes.className, // Tambahkan class ke HTML output
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
default: null, // Nilai default
|
||||||
|
parseHTML: (element) => element.style.backgroundColor || null,
|
||||||
|
renderHTML: (attributes) => {
|
||||||
|
if (!attributes.backgroundColor) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
style: `background-color: ${attributes.backgroundColor};`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
TextAlign.configure({
|
||||||
|
types: ["heading", "paragraph"],
|
||||||
|
}),
|
||||||
|
Underline,
|
||||||
|
Link.configure({
|
||||||
|
openOnClick: false,
|
||||||
|
autolink: true,
|
||||||
|
defaultProtocol: "https",
|
||||||
|
protocols: ["http", "https"],
|
||||||
|
isAllowedUri: (url: any, ctx: any) => {
|
||||||
|
try {
|
||||||
|
// construct URL
|
||||||
|
const parsedUrl = url.includes(":")
|
||||||
|
? new URL(url)
|
||||||
|
: new URL(`${ctx.defaultProtocol}://${url}`);
|
||||||
|
|
||||||
|
// use default validation
|
||||||
|
if (!ctx.defaultValidate(parsedUrl.href)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// disallowed protocols
|
||||||
|
const disallowedProtocols = ["ftp", "file", "mailto"];
|
||||||
|
const protocol = parsedUrl.protocol.replace(":", "");
|
||||||
|
|
||||||
|
if (disallowedProtocols.includes(protocol)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only allow protocols specified in ctx.protocols
|
||||||
|
const allowedProtocols = ctx.protocols.map((p: any) =>
|
||||||
|
typeof p === "string" ? p : p.scheme
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allowedProtocols.includes(protocol)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// disallowed domains
|
||||||
|
const disallowedDomains = [
|
||||||
|
"example-phishing.com",
|
||||||
|
"malicious-site.net",
|
||||||
|
];
|
||||||
|
const domain = parsedUrl.hostname;
|
||||||
|
|
||||||
|
if (disallowedDomains.includes(domain)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all checks have passed
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shouldAutoLink: (url: any) => {
|
||||||
|
try {
|
||||||
|
// construct URL
|
||||||
|
const parsedUrl = url.includes(":")
|
||||||
|
? new URL(url)
|
||||||
|
: new URL(`https://${url}`);
|
||||||
|
|
||||||
|
// only auto-link if the domain is not in the disallowed list
|
||||||
|
const disallowedDomains = [
|
||||||
|
"example-no-autolink.com",
|
||||||
|
"another-no-autolink.com",
|
||||||
|
];
|
||||||
|
const domain = parsedUrl.hostname;
|
||||||
|
|
||||||
|
return !disallowedDomains.includes(domain);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
StarterKit.configure({
|
||||||
|
bulletList: {
|
||||||
|
keepMarks: true,
|
||||||
|
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
||||||
|
},
|
||||||
|
orderedList: {
|
||||||
|
keepMarks: true,
|
||||||
|
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-col relative bg-white border border-gray-300 rounded-md w-full",
|
||||||
|
css`
|
||||||
|
.tiptap h1 {
|
||||||
|
font-size: 1.4rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap h2 {
|
||||||
|
font-size: 1.2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap h3 {
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
.ProseMirror {
|
||||||
|
outline: none !important;
|
||||||
|
padding: 10px 2rem 10px 2rem;
|
||||||
|
}
|
||||||
|
.tiptap a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #313678;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.ProseMirror ul,
|
||||||
|
ol {
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin: 1.25rem 1rem 1.25rem 0.4rem;
|
||||||
|
}
|
||||||
|
.ProseMirror ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
.ProseMirror ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<EditorProvider
|
||||||
|
slotBefore={<MenuBar />}
|
||||||
|
extensions={extensions}
|
||||||
|
onUpdate={({ editor }) => {
|
||||||
|
fm.data[name] = editor.getHTML();
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
content={fm.data[name]}
|
||||||
|
editable={!disabled}
|
||||||
|
></EditorProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import { Input } from "../../ui/input";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
useEditor,
|
||||||
|
EditorContent,
|
||||||
|
useCurrentEditor,
|
||||||
|
EditorProvider,
|
||||||
|
} from "@tiptap/react";
|
||||||
|
import Link from "@tiptap/extension-link";
|
||||||
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
|
import { Color } from "@tiptap/extension-color";
|
||||||
|
import ListItem from "@tiptap/extension-list-item";
|
||||||
|
import TextStyle from "@tiptap/extension-text-style";
|
||||||
|
import { Popover } from "../../Popover/Popover";
|
||||||
|
import { ButtonBetter } from "../../ui/button";
|
||||||
|
import get from "lodash.get";
|
||||||
|
|
||||||
|
export const TypeTag: React.FC<any> = ({
|
||||||
|
name,
|
||||||
|
fm,
|
||||||
|
placeholder,
|
||||||
|
disabled = false,
|
||||||
|
required,
|
||||||
|
type,
|
||||||
|
field,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const [editingIndex, setEditingIndex] = useState<number | null>(null); // Index tag yang sedang diedit
|
||||||
|
const [tempValue, setTempValue] = useState<string>(""); // Nilai sementara untuk pengeditan
|
||||||
|
const tagRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (get(fm, `data.[${name}].length`)) {
|
||||||
|
setTags(fm.data?.[name]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
fm.data[name] = tags;
|
||||||
|
fm.render();
|
||||||
|
}, [inputValue]);
|
||||||
|
const handleSaveEdit = (index: number) => {
|
||||||
|
if (!disabled) return;
|
||||||
|
const updatedTags = [...tags];
|
||||||
|
updatedTags[index] = tempValue.trim(); // Update nilai tag
|
||||||
|
setTags(updatedTags);
|
||||||
|
setEditingIndex(null); // Keluar dari mode edit
|
||||||
|
setTempValue(""); // Reset nilai sementara
|
||||||
|
};
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (!disabled) return;
|
||||||
|
if (e.key === "Enter" && inputValue) {
|
||||||
|
e.preventDefault();
|
||||||
|
setTags([...tags, inputValue]);
|
||||||
|
setInputValue("");
|
||||||
|
} else if (e.key === "Backspace" && !inputValue && tags.length > 0) {
|
||||||
|
setTags(tags.slice(0, -1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleFocusTag = (index: number) => {
|
||||||
|
if (!disabled) return;
|
||||||
|
setEditingIndex(index); // Masuk ke mode edit
|
||||||
|
setTempValue(tags[index]); // Isi nilai sementara dengan nilai tag
|
||||||
|
setTimeout(() => {
|
||||||
|
tagRefs.current[index]?.focus(); // Fokus pada elemen yang diedit
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
const removeTag = (index: number) => {
|
||||||
|
if (!disabled) return;
|
||||||
|
setTags(tags.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap items-center border border-gray-300 rounded-md flex-grow ">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex flex-row items-center bg-blue-100 text-blue-800 rounded-full m-1 text-sm"
|
||||||
|
>
|
||||||
|
{disabled ? (
|
||||||
|
<div className="px-2">{tag}</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"px-3 py-1 pr-0 flex-grow focus:shadow-none focus:ring-0 focus:border-none focus:outline-none",
|
||||||
|
editingIndex! !== index && "cursor-pointer"
|
||||||
|
)}
|
||||||
|
contentEditable={editingIndex === index}
|
||||||
|
suppressContentEditableWarning
|
||||||
|
onBlur={() => handleSaveEdit(index)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (!disabled) return;
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSaveEdit(index);
|
||||||
|
}
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
setEditingIndex(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
handleFocusTag(index);
|
||||||
|
}}
|
||||||
|
onInput={(e) =>
|
||||||
|
setTempValue((e.target as HTMLDivElement).innerText)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!disabled && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeTag(index)}
|
||||||
|
className="ml-2 text-blue-500 hover:text-blue-700 pr-2"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{!disabled && (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
className="rounded-md flex-grow border-none outline-none text-sm focus:shadow-none focus:ring-0 focus:border-none focus:outline-none"
|
||||||
|
placeholder="Add a option..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -446,6 +446,10 @@ export const Typeahead: FC<{
|
||||||
}
|
}
|
||||||
local.open = open;
|
local.open = open;
|
||||||
local.render();
|
local.render();
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
resetSearch();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
showEmpty={!allow_new}
|
showEmpty={!allow_new}
|
||||||
className={popupClassName}
|
className={popupClassName}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import {
|
import { Avatar, Dropdown, Navbar } from "flowbite-react";
|
||||||
Avatar,
|
|
||||||
DarkThemeToggle,
|
|
||||||
Dropdown,
|
|
||||||
Label,
|
|
||||||
Navbar,
|
|
||||||
TextInput,
|
|
||||||
} from "flowbite-react";
|
|
||||||
import {
|
import {
|
||||||
HiArchive,
|
HiArchive,
|
||||||
HiBell,
|
HiBell,
|
||||||
|
|
@ -16,50 +9,27 @@ import {
|
||||||
HiEye,
|
HiEye,
|
||||||
HiInbox,
|
HiInbox,
|
||||||
HiLogout,
|
HiLogout,
|
||||||
HiMenuAlt1,
|
|
||||||
HiOutlineTicket,
|
HiOutlineTicket,
|
||||||
HiSearch,
|
|
||||||
HiShoppingBag,
|
HiShoppingBag,
|
||||||
HiUserCircle,
|
HiUserCircle,
|
||||||
HiUsers,
|
HiUsers,
|
||||||
HiViewGrid,
|
HiViewGrid,
|
||||||
HiX,
|
|
||||||
} from "react-icons/hi";
|
} from "react-icons/hi";
|
||||||
import { siteurl } from "@/lib/utils/siteurl";
|
import { siteurl } from "@/lib/utils/siteurl";
|
||||||
import { get_user } from "@/lib/utils/get_user";
|
import { get_user } from "@/lib/utils/get_user";
|
||||||
import api from "@/lib/utils/axios";
|
import api from "@/lib/utils/axios";
|
||||||
const NavFlow: React.FC<any> = ({ minimaze }) => {
|
const NavFlow: React.FC<any> = ({ minimaze }) => {
|
||||||
return (
|
return (
|
||||||
<Navbar fluid>
|
<Navbar fluid className="bg-transparent pt-0 pr-6 pb-0">
|
||||||
<div className="w-full p-1 lg:px-5 lg:pl-3">
|
<div className="w-full p-1 lg:px-5 lg:pl-3 rounded rounded-lg">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center"></div>
|
||||||
{true && (
|
<div className="flex flex-row gap-x-3 justify-center ">
|
||||||
<button
|
<div className="flex flex-row items-center flex-grow">
|
||||||
onClick={minimaze}
|
|
||||||
className="mr-3 cursor-pointer rounded p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 lg:inline"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Toggle sidebar</span>
|
|
||||||
<HiMenuAlt1 className="h-6 w-6" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<Navbar.Brand href="/">
|
|
||||||
<img
|
|
||||||
alt=""
|
|
||||||
src={siteurl("/julong.png")}
|
|
||||||
className="mr-3 h-6 sm:h-8"
|
|
||||||
/>
|
|
||||||
<span className="self-center whitespace-nowrap text-2xl font-semibold text-black">
|
|
||||||
Man Power Management
|
|
||||||
</span>
|
|
||||||
</Navbar.Brand>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center lg:gap-3">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<NotificationBellDropdown />
|
<NotificationBellDropdown />
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:block">
|
|
||||||
|
<div className="hidden lg:flex flex-row justify-center">
|
||||||
<UserDropdown />
|
<UserDropdown />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -403,10 +373,16 @@ const UserDropdown: FC = function () {
|
||||||
arrowIcon={false}
|
arrowIcon={false}
|
||||||
inline
|
inline
|
||||||
label={
|
label={
|
||||||
<span>
|
<div className="flex flex-row justify-center">
|
||||||
<span className="sr-only">User menu</span>
|
<div className="flex flex-row items-center flex-grow">
|
||||||
|
<div className="border-l border-gray-200 px-2 h-full flex items-end justify-center flex-col text-xs max-w-[100px]">
|
||||||
|
<div>
|
||||||
|
{get_user("employee.name") ? get_user("employee.name") : "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Avatar alt="" img={siteurl("/dog.jpg")} rounded size="sm" />
|
<Avatar alt="" img={siteurl("/dog.jpg")} rounded size="sm" />
|
||||||
</span>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Dropdown.Header>
|
<Dropdown.Header>
|
||||||
|
|
@ -430,8 +406,10 @@ const UserDropdown: FC = function () {
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await api.delete(process.env.NEXT_PUBLIC_BASE_URL + "/api/destroy-cookies");
|
await api.delete(
|
||||||
localStorage.removeItem('user');
|
process.env.NEXT_PUBLIC_BASE_URL + "/api/destroy-cookies"
|
||||||
|
);
|
||||||
|
localStorage.removeItem("user");
|
||||||
if (typeof window === "object")
|
if (typeof window === "object")
|
||||||
navigate(`${process.env.NEXT_PUBLIC_API_PORTAL}/logout`);
|
navigate(`${process.env.NEXT_PUBLIC_API_PORTAL}/logout`);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
const renderTree = (items: TreeMenuItem[], depth: number = 0) => {
|
const renderTree = (items: TreeMenuItem[], depth: number = 0) => {
|
||||||
return items.map((item, index) => {
|
return items.map((item, index) => {
|
||||||
const hasChildren = item.children && item.children.length > 0;
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
const isActive = item.href && detectCase(currentPage, item.href);
|
let isActive = item.href && detectCase(currentPage, item.href);
|
||||||
const isParentActive = hasChildren && isChildActive(item.children!);
|
let isParentActive = hasChildren && isChildActive(item.children!);
|
||||||
const [isOpen, setIsOpen] = useState(isParentActive);
|
const [isOpen, setIsOpen] = useState(isParentActive);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isParentActive) {
|
if (isParentActive) {
|
||||||
|
|
@ -71,14 +71,41 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={item.href || item.title || index}>
|
<React.Fragment key={item.href || item.title || index}>
|
||||||
{hasChildren ? (
|
{hasChildren ? (
|
||||||
<li>
|
<li className="relative">
|
||||||
|
{mini && isParentActive && (
|
||||||
|
<div
|
||||||
|
className={cx("absolute top-[-15px] right-[-1px] text-layer")}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="184"
|
||||||
|
height="167"
|
||||||
|
viewBox="0 0 184 167"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M17 167C109.232 167 184 92.2316 184 0V167H17ZM0 166.145C5.58984 166.711 11.2611 167 17 167H0V166.145Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
" flex-row flex items-center cursor-pointer items-center w-full rounded-lg text-base font-normal text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-700 flex flex-row",
|
"relative flex-row flex items-center cursor-pointer items-center w-full rounded-full rounded-r-none text-base font-normal text-gray-900 flex flex-row",
|
||||||
isParentActive && !depth
|
|
||||||
? " text-base font-normal text-dark-500 rounded-lg hover:bg-gray-200 group bg-white shadow-md shadow-[#31367875] hover:!bg-white transition-all duration-200 dark:bg-gray-700"
|
mini
|
||||||
: " ",
|
? isParentActive && !depth
|
||||||
mini ? "m-0 flex-grow w-full" : "py-2.5 px-4 ",
|
? " text-base font-normal text-primary rounded-full rounded-r-none group bg-layer transition-all duration-200 dark:bg-gray-700"
|
||||||
|
: " text-white"
|
||||||
|
: isActive && !depth
|
||||||
|
? " text-base font-normal text-primary rounded-full rounded-r-none group bg-layer transition-all duration-200 dark:bg-gray-700"
|
||||||
|
: " text-white",
|
||||||
|
mini ? "pr-4 m-0 flex-grow w-full" : "py-2.5 px-4 ",
|
||||||
mini
|
mini
|
||||||
? css`
|
? css`
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
|
@ -96,24 +123,30 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"flex flex-row items-center flex-grow",
|
"flex flex-row items-center flex-grow",
|
||||||
mini ? "py-2 justify-center rounded-lg" : " px-3",
|
mini
|
||||||
|
? "py-2 justify-center rounded-full rounded-r-none"
|
||||||
|
: " px-3",
|
||||||
mini
|
mini
|
||||||
? isParentActive
|
? isParentActive
|
||||||
? "bg-[#313678]"
|
? "bg-layer font-bold "
|
||||||
: "bg-white hover:bg-gray-300 shadow shadow-gray-300"
|
: "bg-primary text-white"
|
||||||
: ""
|
: isActive
|
||||||
|
? "font-bold text-white "
|
||||||
|
: "text-white"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!depth ? (
|
{!depth ? (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
" w-8 h-8 rounded-lg text-center flex flex-row items-center justify-center",
|
" w-8 h-8 text-center flex flex-row items-center justify-center",
|
||||||
isParentActive
|
mini
|
||||||
? "bg-[#313678] text-white active-menu-icon"
|
? isParentActive
|
||||||
: "bg-white shadow-lg text-black",
|
? "text-primary "
|
||||||
!mini
|
: " text-white"
|
||||||
? "mr-1 p-2 shadow-gray-300"
|
: isActive
|
||||||
: " text-lg shadow-none",
|
? "text-primary "
|
||||||
|
: " text-white",
|
||||||
|
!mini ? "mr-1 p-2 " : " text-lg ",
|
||||||
mini
|
mini
|
||||||
? css`
|
? css`
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
|
|
@ -129,10 +162,10 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
|
|
||||||
{!mini ? (
|
{!mini ? (
|
||||||
<>
|
<>
|
||||||
<div className="pl-2 flex-grow text-black text-xs">
|
<div className="pl-2 flex-grow text-xs">
|
||||||
{item.title}
|
{item.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-md">
|
<div className="text-md px-1">
|
||||||
{isOpen ? <FaChevronUp /> : <FaChevronDown />}
|
{isOpen ? <FaChevronUp /> : <FaChevronDown />}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
@ -140,8 +173,27 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{mini && isParentActive && (
|
||||||
|
<div className=" absolute bottom-[-15px] right-[-1px] text-layer">
|
||||||
|
<svg
|
||||||
|
width="147"
|
||||||
|
height="147"
|
||||||
|
viewBox="0 0 147 147"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M0 0H147V147C147 65.8141 81.1859 0 0 0Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<Sidebar.ItemGroup
|
<Sidebar.ItemGroup
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"border-none mt-0",
|
"border-none mt-0",
|
||||||
|
|
@ -153,22 +205,46 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
</Sidebar.ItemGroup>
|
</Sidebar.ItemGroup>
|
||||||
</li>
|
</li>
|
||||||
) : (
|
) : (
|
||||||
<li>
|
<li className="relative">
|
||||||
|
{isActive && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
" absolute top-[-15px] right-[-1px] text-layer"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="184"
|
||||||
|
height="167"
|
||||||
|
viewBox="0 0 184 167"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M17 167C109.232 167 184 92.2316 184 0V167H17ZM0 166.145C5.58984 166.711 11.2611 167 17 167H0V166.145Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<SidebarLinkBetter
|
<SidebarLinkBetter
|
||||||
href={item.href}
|
href={item.href}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item?.href) setCurrentPage(item.href);
|
if (item?.href) setCurrentPage(item.href);
|
||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
" flex-row flex items-center cursor-pointer items-center w-full rounded-lg text-base font-normal text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-700 flex flex-row py-2.5 px-4",
|
"relative flex-row flex items-center cursor-pointer items-center w-full rounded-full rounded-r-none text-base text-gray-900 flex flex-row py-2.5 px-4",
|
||||||
isActive
|
isActive
|
||||||
? " py-2.5 px-4 text-base font-normal text-dark-500 rounded-lg group shadow-[#31367875] transition-all duration-200 dark:bg-gray-700"
|
? " py-2.5 px-4 text-base rounded-full rounded-r-none group "
|
||||||
: "",
|
: " font-normal",
|
||||||
|
mini ? "transition-all duration-200" : "",
|
||||||
isActive
|
isActive
|
||||||
? !depth
|
? !depth
|
||||||
? " bg-white shadow-md hover:bg-gray-200 hover:!bg-white "
|
? " bg-layer font-normal"
|
||||||
: "bg-gray-100"
|
: " bg-layer text-primary font-bold"
|
||||||
: "",
|
: "text-white",
|
||||||
css`
|
css`
|
||||||
& > span {
|
& > span {
|
||||||
white-space: wrap !important;
|
white-space: wrap !important;
|
||||||
|
|
@ -183,10 +259,8 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
{!depth ? (
|
{!depth ? (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
" shadow-gray-300 text-dark-700 w-8 h-8 rounded-lg text-center flex flex-row items-center justify-center shadow-[#313678]",
|
" text-dark-700 w-8 h-8 rounded-lg text-center flex flex-row items-center justify-center ",
|
||||||
isActive
|
isActive ? "bg-[#313678] " : "bg-layer text-white",
|
||||||
? "bg-[#313678] text-white"
|
|
||||||
: "bg-white shadow-lg text-black",
|
|
||||||
!mini ? "mr-1 p-2" : " text-lg"
|
!mini ? "mr-1 p-2" : " text-lg"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -197,14 +271,35 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
)}
|
)}
|
||||||
{!mini ? (
|
{!mini ? (
|
||||||
<>
|
<>
|
||||||
<div className="pl-2 text-black text-xs">
|
<div className="pl-2 text-xs">{item.title}</div>
|
||||||
{item.title}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{isActive && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"absolute bottom-[-15px] right-[-1px] text-layer"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="147"
|
||||||
|
height="147"
|
||||||
|
viewBox="0 0 147 147"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M0 0H147V147C147 65.8141 81.1859 0 0 0Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</SidebarLinkBetter>
|
</SidebarLinkBetter>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|
@ -214,46 +309,27 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames("flex h-full lg:!block", {})}>
|
<div className={classNames("flex h-full lg:!block ", {})}>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
aria-label="Sidebar with multi-level dropdown example"
|
aria-label="Sidebar with multi-level dropdown example"
|
||||||
className={classNames("relative bg-white", mini ? "w-20" : "", css``)}
|
className={classNames(
|
||||||
|
"relative bg-primary pt-0 sidebar",
|
||||||
|
mini ? "w-20" : "",
|
||||||
|
css`
|
||||||
|
> div {
|
||||||
|
background: transparent;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{/* {!local.ready ? (
|
<div className="w-full h-full relative ">
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
"absolute",
|
|
||||||
css`
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex flex-grow flex-row items-center justify-center">
|
|
||||||
<div className="flex flex-col gap-y-2">
|
|
||||||
<div className="flex flex-row gap-x-2">
|
|
||||||
<Skeleton className="h-24 flex-grow" />
|
|
||||||
<Skeleton className="h-24 flex-grow" />
|
|
||||||
</div>
|
|
||||||
<Skeleton className="h-24 w-[230px]" />
|
|
||||||
<div className="flex flex-row gap-x-2">
|
|
||||||
<Skeleton className="h-24 flex-grow" />
|
|
||||||
<Skeleton className="h-24 flex-grow" />
|
|
||||||
</div>
|
|
||||||
<Skeleton className="h-24 w-[230px]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
<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 absolute top-0 left-0">
|
||||||
<Sidebar.Items>
|
<Sidebar.Items>
|
||||||
<Sidebar.ItemGroup
|
<Sidebar.ItemGroup
|
||||||
className={cx(
|
className={cx(
|
||||||
"border-none mt-0",
|
"border-none mt-0 pt-4",
|
||||||
mini ? "flex flex-col gap-y-2" : ""
|
mini ? "flex flex-col gap-y-2" : ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import React, { FC, useCallback, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
|
||||||
Label,
|
Label,
|
||||||
Modal,
|
Modal,
|
||||||
Table,
|
Table,
|
||||||
|
|
@ -44,12 +43,15 @@ import { InputSearch } from "../ui/input-search";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { FaChevronDown } from "react-icons/fa";
|
import { FaChevronDown } from "react-icons/fa";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
|
import { Checkbox } from "../ui/checkbox";
|
||||||
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
|
import { formatMoney } from "../form/field/TypeInput";
|
||||||
|
|
||||||
export const TableList: React.FC<any> = ({
|
export const TableList: React.FC<any> = ({
|
||||||
name,
|
name,
|
||||||
column,
|
column,
|
||||||
onLoad,
|
onLoad,
|
||||||
take = 50,
|
take = 20,
|
||||||
header,
|
header,
|
||||||
disabledPagination,
|
disabledPagination,
|
||||||
disabledHeader,
|
disabledHeader,
|
||||||
|
|
@ -57,6 +59,8 @@ export const TableList: React.FC<any> = ({
|
||||||
hiddenNoRow,
|
hiddenNoRow,
|
||||||
disabledHoverRow,
|
disabledHoverRow,
|
||||||
onInit,
|
onInit,
|
||||||
|
onCount,
|
||||||
|
feature,
|
||||||
}) => {
|
}) => {
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const sideLeft =
|
const sideLeft =
|
||||||
|
|
@ -71,11 +75,16 @@ export const TableList: React.FC<any> = ({
|
||||||
status: string;
|
status: string;
|
||||||
progress: number;
|
progress: number;
|
||||||
};
|
};
|
||||||
|
const checkbox =
|
||||||
|
Array.isArray(feature) && feature?.length
|
||||||
|
? feature.includes("checkbox")
|
||||||
|
: false;
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
table: null as any,
|
table: null as any,
|
||||||
data: [] as any[],
|
data: [] as any[],
|
||||||
sort: {} as any,
|
sort: {} as any,
|
||||||
search: null as any,
|
search: null as any,
|
||||||
|
count: 0 as any,
|
||||||
addRow: (row: any) => {
|
addRow: (row: any) => {
|
||||||
setData((prev) => [...prev, row]);
|
setData((prev) => [...prev, row]);
|
||||||
local.data.push(row);
|
local.data.push(row);
|
||||||
|
|
@ -145,51 +154,45 @@ export const TableList: React.FC<any> = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof onInit === "function") {
|
const run = async () => {
|
||||||
onInit(local);
|
toast.info(
|
||||||
}
|
<>
|
||||||
toast.info(
|
<Loader2
|
||||||
<>
|
className={cx(
|
||||||
<Loader2
|
"h-4 w-4 animate-spin-important",
|
||||||
className={cx(
|
css`
|
||||||
"h-4 w-4 animate-spin-important",
|
animation: spin 1s linear infinite !important;
|
||||||
css`
|
@keyframes spin {
|
||||||
animation: spin 1s linear infinite !important;
|
0% {
|
||||||
@keyframes spin {
|
transform: rotate(0deg);
|
||||||
0% {
|
}
|
||||||
transform: rotate(0deg);
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
100% {
|
`
|
||||||
transform: rotate(360deg);
|
)}
|
||||||
}
|
/>
|
||||||
}
|
{"Loading..."}
|
||||||
`
|
</>
|
||||||
)}
|
);
|
||||||
/>
|
if (typeof onCount === "function") {
|
||||||
{"Loading..."}
|
const res = await onCount();
|
||||||
</>
|
local.count = res;
|
||||||
);
|
local.render();
|
||||||
if (Array.isArray(onLoad)) {
|
}
|
||||||
local.data = onLoad;
|
|
||||||
local.render();
|
if (Array.isArray(onLoad)) {
|
||||||
setData(onLoad);
|
local.data = onLoad;
|
||||||
} else {
|
local.render();
|
||||||
const res: any = onLoad({
|
setData(onLoad);
|
||||||
search: local.search,
|
|
||||||
sort: local.sort,
|
|
||||||
take,
|
|
||||||
paging: 1,
|
|
||||||
});
|
|
||||||
if (res instanceof Promise) {
|
|
||||||
res.then((e) => {
|
|
||||||
local.data = e;
|
|
||||||
local.render();
|
|
||||||
setData(e);
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.dismiss();
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
|
const res: any = await onLoad({
|
||||||
|
search: local.search,
|
||||||
|
sort: local.sort,
|
||||||
|
take,
|
||||||
|
paging: 1,
|
||||||
|
});
|
||||||
local.data = res;
|
local.data = res;
|
||||||
local.render();
|
local.render();
|
||||||
setData(res);
|
setData(res);
|
||||||
|
|
@ -197,13 +200,53 @@ export const TableList: React.FC<any> = ({
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if (typeof onInit === "function") {
|
||||||
|
onInit(local);
|
||||||
}
|
}
|
||||||
|
run();
|
||||||
}, []);
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log("PERUBAHAN");
|
||||||
|
}, [data]);
|
||||||
|
const objectNull = {};
|
||||||
const defaultColumns: ColumnDef<Person>[] = init_column(column);
|
const defaultColumns: ColumnDef<Person>[] = init_column(column);
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [columns] = React.useState<typeof defaultColumns>(() => [
|
const [columns] = React.useState<typeof defaultColumns>(() =>
|
||||||
...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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="px-0.5 items-center justify-center flex flex-row">
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onClick={(e) => {
|
||||||
|
const handler = row.getToggleSelectedHandler();
|
||||||
|
handler(e); // Pastikan ini memanggil fungsi handler yang benar
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
...defaultColumns,
|
||||||
|
]
|
||||||
|
: [...defaultColumns]
|
||||||
|
);
|
||||||
const [columnResizeMode, setColumnResizeMode] =
|
const [columnResizeMode, setColumnResizeMode] =
|
||||||
React.useState<ColumnResizeMode>("onChange");
|
React.useState<ColumnResizeMode>("onChange");
|
||||||
|
|
||||||
|
|
@ -218,25 +261,29 @@ export const TableList: React.FC<any> = ({
|
||||||
: {
|
: {
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
};
|
};
|
||||||
|
const [pagination, setPagination] = React.useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: data,
|
data: data,
|
||||||
columnResizeMode,
|
columnResizeMode,
|
||||||
|
pageCount: Math.ceil(local.count / 20),
|
||||||
|
manualPagination: true,
|
||||||
columnResizeDirection,
|
columnResizeDirection,
|
||||||
columns,
|
columns,
|
||||||
|
enableRowSelection: true,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
initialState: {
|
initialState: {
|
||||||
pagination: {
|
pagination: {
|
||||||
pageIndex: 0, //custom initial page index
|
pageIndex: 0,
|
||||||
pageSize: 25, //custom default page size
|
pageSize: 20, //custom default page size
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
pagination: {
|
pagination,
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: 50,
|
|
||||||
},
|
|
||||||
sorting,
|
sorting,
|
||||||
},
|
},
|
||||||
...paginationConfig,
|
...paginationConfig,
|
||||||
|
|
@ -263,19 +310,9 @@ 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 border-b border-gray-200 bg-white p-4 sm:flex">
|
<div className="head-tbl-list block items-start justify-between bg-white px-0 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">
|
||||||
{false ? (
|
|
||||||
<div className="">
|
|
||||||
<h2 className="text-xl font-semibold text-gray-900 sm:text-2xl">
|
|
||||||
All <span className="">{name ? `${name}s` : ``}</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{sideLeft ? (
|
{sideLeft ? (
|
||||||
sideLeft(local)
|
sideLeft(local)
|
||||||
|
|
@ -333,9 +370,35 @@ export const TableList: React.FC<any> = ({
|
||||||
<div className="overflow-auto relative flex-grow flex-row">
|
<div className="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="tbl absolute top-0 left-0 inline-block flex-grow w-full h-full align-middle">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Table className="min-w-full divide-y divide-gray-200 ">
|
<Table
|
||||||
|
className={cx(
|
||||||
|
"min-w-full divide-y divide-gray-200 ",
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
{!disabledHeadTable ? (
|
{!disabledHeadTable ? (
|
||||||
<thead className="text-md bg-second group/head text-md uppercase text-gray-700 sticky top-0">
|
<thead className="rounded-md overflow-hidden text-md bg-second group/head text-md uppercase text-gray-700 sticky top-0">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr
|
<tr
|
||||||
key={`${headerGroup.id}`}
|
key={`${headerGroup.id}`}
|
||||||
|
|
@ -347,14 +410,26 @@ export const TableList: React.FC<any> = ({
|
||||||
(e: any) => e?.name === name
|
(e: any) => e?.name === name
|
||||||
);
|
);
|
||||||
const isSort =
|
const isSort =
|
||||||
typeof col?.sortable === "boolean"
|
name === "select"
|
||||||
|
? false
|
||||||
|
: typeof col?.sortable === "boolean"
|
||||||
? col.sortable
|
? col.sortable
|
||||||
: true;
|
: true;
|
||||||
|
const resize =
|
||||||
|
name === "select"
|
||||||
|
? false
|
||||||
|
: typeof col?.resize === "boolean"
|
||||||
|
? col.resize
|
||||||
|
: true;
|
||||||
return (
|
return (
|
||||||
<th
|
<th
|
||||||
{...{
|
{...{
|
||||||
style: {
|
style: {
|
||||||
width: col?.width
|
width: !resize
|
||||||
|
? `${col.width}px`
|
||||||
|
: name === "select"
|
||||||
|
? `${5}px`
|
||||||
|
: col?.width
|
||||||
? header.getSize() < col?.width
|
? header.getSize() < col?.width
|
||||||
? `${col.width}px`
|
? `${col.width}px`
|
||||||
: header.getSize()
|
: header.getSize()
|
||||||
|
|
@ -363,7 +438,13 @@ export const TableList: React.FC<any> = ({
|
||||||
}}
|
}}
|
||||||
key={header.id}
|
key={header.id}
|
||||||
colSpan={header.colSpan}
|
colSpan={header.colSpan}
|
||||||
className="relative px-2 py-2 text-sm py-1 "
|
className={cx(
|
||||||
|
"relative px-2 py-2 text-sm py-1 uppercase",
|
||||||
|
name === "select" &&
|
||||||
|
css`
|
||||||
|
max-width: 5px;
|
||||||
|
`
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
key={`${header.id}-label`}
|
key={`${header.id}-label`}
|
||||||
|
|
@ -398,7 +479,12 @@ export const TableList: React.FC<any> = ({
|
||||||
isSort ? " cursor-pointer" : ""
|
isSort ? " cursor-pointer" : ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center flex-grow text-sm">
|
<div
|
||||||
|
className={cx(
|
||||||
|
"flex flex-row items-center flex-grow text-sm capitalize",
|
||||||
|
name === "select" ? "justify-center" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder
|
||||||
? null
|
? null
|
||||||
: flexRender(
|
: flexRender(
|
||||||
|
|
@ -438,13 +524,19 @@ export const TableList: React.FC<any> = ({
|
||||||
header.column.resetSize(),
|
header.column.resetSize(),
|
||||||
onMouseDown: header.getResizeHandler(),
|
onMouseDown: header.getResizeHandler(),
|
||||||
onTouchStart: header.getResizeHandler(),
|
onTouchStart: header.getResizeHandler(),
|
||||||
className: `resizer w-0.5 bg-gray-300 ${
|
className: cx(
|
||||||
table.options.columnResizeDirection
|
`resizer bg-[#b3c9fe] cursor-e-resize ${
|
||||||
} ${
|
table.options.columnResizeDirection
|
||||||
header.column.getIsResizing()
|
} ${
|
||||||
? "isResizing"
|
header.column.getIsResizing()
|
||||||
: ""
|
? "isResizing"
|
||||||
}`,
|
: ""
|
||||||
|
}`,
|
||||||
|
css`
|
||||||
|
width: 1px;
|
||||||
|
cursor: e-resize !important;
|
||||||
|
`
|
||||||
|
),
|
||||||
style: {
|
style: {
|
||||||
transform:
|
transform:
|
||||||
columnResizeMode === "onEnd" &&
|
columnResizeMode === "onEnd" &&
|
||||||
|
|
@ -474,15 +566,16 @@ export const TableList: React.FC<any> = ({
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Table.Body className="divide-y divide-gray-200 bg-white">
|
<Table.Body className="divide-y border-none bg-white">
|
||||||
{table.getRowModel().rows.map((row, idx) => (
|
{table.getRowModel().rows.map((row, idx) => (
|
||||||
<Table.Row
|
<Table.Row
|
||||||
key={row.id}
|
key={row.id}
|
||||||
className={cx(
|
className={cx(
|
||||||
disabledHoverRow ? "" : "hover:bg-[#DBDBE7]",
|
disabledHoverRow ? "" : "hover:bg-gray-100",
|
||||||
css`
|
css`
|
||||||
height: 44px;
|
height: 44px;
|
||||||
`
|
`,
|
||||||
|
"border-none"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell) => {
|
{row.getVisibleCells().map((cell) => {
|
||||||
|
|
@ -542,11 +635,19 @@ export const TableList: React.FC<any> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Pagination
|
<Pagination
|
||||||
|
list={local}
|
||||||
|
count={local.count}
|
||||||
onNextPage={() => table.nextPage()}
|
onNextPage={() => table.nextPage()}
|
||||||
onPrevPage={() => table.previousPage()}
|
onPrevPage={() => table.previousPage()}
|
||||||
disabledNextPage={!table.getCanNextPage()}
|
disabledNextPage={!table.getCanNextPage()}
|
||||||
disabledPrevPage={!table.getCanPreviousPage()}
|
disabledPrevPage={!table.getCanPreviousPage()}
|
||||||
page={table.getState().pagination.pageIndex + 1}
|
page={table.getState().pagination.pageIndex + 1}
|
||||||
|
setPage={(page: any) => {
|
||||||
|
setPagination({
|
||||||
|
pageIndex: page,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
}}
|
||||||
countPage={table.getPageCount()}
|
countPage={table.getPageCount()}
|
||||||
countData={local.data.length}
|
countData={local.data.length}
|
||||||
take={take}
|
take={take}
|
||||||
|
|
@ -565,138 +666,139 @@ export const Pagination: React.FC<any> = ({
|
||||||
disabledNextPage,
|
disabledNextPage,
|
||||||
disabledPrevPage,
|
disabledPrevPage,
|
||||||
page,
|
page,
|
||||||
countPage,
|
count,
|
||||||
countData,
|
list,
|
||||||
take,
|
setPage,
|
||||||
onChangePage,
|
onChangePage,
|
||||||
}) => {
|
}) => {
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
page: 1 as any,
|
page: 1 as any,
|
||||||
|
pagination: [] as any,
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
local.page = page;
|
local.page = page;
|
||||||
|
local.pagination = getPagination(page, Math.ceil(count / 20));
|
||||||
local.render();
|
local.render();
|
||||||
}, [page]);
|
}, [page, count]);
|
||||||
return (
|
return (
|
||||||
<div className="tbl-pagination sticky text-sm bottom-0 right-0 w-full items-center justify-end text-sm border-t border-gray-200 bg-white p-4 sm:flex">
|
<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="mb-4 flex items-center sm:mb-0">
|
<div className="flex flex-row items-center text-gray-600">
|
||||||
<div
|
Showing {local.page * 20 - 19} to{" "}
|
||||||
onClick={() => {
|
{list.data?.length >= 20
|
||||||
if (!disabledPrevPage) {
|
? local.page * 20
|
||||||
onPrevPage();
|
: local.page === 1 && Math.ceil(count / 20) === 1
|
||||||
}
|
? list.data?.length
|
||||||
}}
|
: local.page * 20 - 19 + list.data?.length}{" "}
|
||||||
className={classNames(
|
of {formatMoney(getNumber(count))} results
|
||||||
"inline-flex justify-center rounded p-1 ",
|
|
||||||
disabledPrevPage
|
|
||||||
? "text-gray-200"
|
|
||||||
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Previous page</span>
|
|
||||||
<HiChevronLeft className="text-2xl" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
if (!disabledNextPage) {
|
|
||||||
onNextPage();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={classNames(
|
|
||||||
"inline-flex justify-center rounded p-1 ",
|
|
||||||
disabledNextPage
|
|
||||||
? "text-gray-200"
|
|
||||||
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Next page</span>
|
|
||||||
<HiChevronRight className="text-2xl" />
|
|
||||||
</div>
|
|
||||||
<span className="text-md font-normal text-gray-500">
|
|
||||||
Page
|
|
||||||
<span className="font-semibold text-gray-900">{page}</span>
|
|
||||||
of
|
|
||||||
<span className="font-semibold text-gray-900">{countPage}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="flex items-center pl-2 text-black gap-x-2">
|
|
||||||
| Go to page:
|
|
||||||
<form
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const page = Number(local.page);
|
|
||||||
if (!page) {
|
|
||||||
local.page = 0;
|
|
||||||
} else if (page > countPage) {
|
|
||||||
local.page = countPage;
|
|
||||||
}
|
|
||||||
local.render();
|
|
||||||
onChangePage(local.page - 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max={countPage}
|
|
||||||
value={local.page}
|
|
||||||
onChange={(e) => {
|
|
||||||
local.page = e.target.value;
|
|
||||||
local.render();
|
|
||||||
debouncedHandler(() => {
|
|
||||||
const page = Number(local.page);
|
|
||||||
if (!page) {
|
|
||||||
local.page = 0;
|
|
||||||
} else if (page > countPage) {
|
|
||||||
local.page = countPage;
|
|
||||||
}
|
|
||||||
local.render();
|
|
||||||
onChangePage(local.page - 1);
|
|
||||||
}, 1500);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-3 hidden">
|
<div className="flex flex-row justify-center">
|
||||||
{!disabledPrevPage ? (
|
<div>
|
||||||
<>
|
<nav
|
||||||
<div
|
className="isolate inline-flex -space-x-px flex flex-row items-center gap-x-2"
|
||||||
onClick={() => {
|
aria-label="Pagination"
|
||||||
if (!disabledPrevPage) {
|
>
|
||||||
onPrevPage();
|
{local.pagination.map((e: any, idx: number) => {
|
||||||
}
|
return (
|
||||||
}}
|
<div
|
||||||
className={classNames(
|
key={"page_" + idx}
|
||||||
"cursor-pointer inline-flex flex-1 items-center justify-center rounded-lg bg-primary px-3 py-2 text-center text-md font-medium text-white hover:bg-primary focus:ring-4 focus:ring-primary-300"
|
onClick={() => {
|
||||||
)}
|
if (e?.label !== "...") {
|
||||||
>
|
local.page = getNumber(e?.label);
|
||||||
<HiChevronLeft className="mr-1 text-base" />
|
local.render();
|
||||||
Previous
|
onChangePage(local.page - 1);
|
||||||
</div>
|
setPage(local.page - 1);
|
||||||
</>
|
list.reload();
|
||||||
) : (
|
}
|
||||||
<></>
|
}}
|
||||||
)}
|
className={cx(
|
||||||
{!disabledNextPage ? (
|
"text-sm px-2 py-1",
|
||||||
<>
|
e.active
|
||||||
<div
|
? "relative z-10 inline-flex items-center bg-primary font-semibold text-white rounded-md"
|
||||||
onClick={() => {
|
: e?.label === "..."
|
||||||
if (!disabledNextPage) {
|
? "relative z-10 inline-flex items-center font-semibold text-gray-800 rounded-md"
|
||||||
onNextPage();
|
: "cursor-pointer relative z-10 inline-flex items-center hover:bg-gray-100 font-semibold text-gray-800 rounded-md"
|
||||||
}
|
)}
|
||||||
}}
|
>
|
||||||
className={classNames(
|
{e?.label}
|
||||||
"cursor-pointer inline-flex flex-1 items-center justify-center rounded-lg bg-primary px-3 py-2 text-center text-md font-medium text-white hover:bg-primary focus:ring-4 focus:ring-primary-300"
|
</div>
|
||||||
)}
|
);
|
||||||
>
|
})}
|
||||||
Next
|
</nav>
|
||||||
<HiChevronRight className="ml-1 text-base" />
|
</div>
|
||||||
</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>
|
||||||
</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,349 @@
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
COLORS,
|
||||||
|
DATE_FORMAT,
|
||||||
|
DEFAULT_COLOR,
|
||||||
|
LANGUAGE,
|
||||||
|
} from "./Datepicker/constants";
|
||||||
|
import { ColorKeys, DatepickerType, Period } from "./Datepicker/types";
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import { formatDate, nextMonth, previousMonth } from "./Datepicker/helpers";
|
||||||
|
import useOnClickOutside from "./Datepicker/hooks";
|
||||||
|
import DatepickerContext from "./Datepicker/contexts/DatepickerContext";
|
||||||
|
import Calendar from "./Datepicker/components/Calendar";
|
||||||
|
const CalenderFull: React.FC<DatepickerType> = ({
|
||||||
|
primaryColor = "blue",
|
||||||
|
value = null,
|
||||||
|
onChange,
|
||||||
|
useRange = true,
|
||||||
|
showFooter = false,
|
||||||
|
showShortcuts = false,
|
||||||
|
configs = undefined,
|
||||||
|
asSingle = false,
|
||||||
|
placeholder = null,
|
||||||
|
separator = "~",
|
||||||
|
startFrom = null,
|
||||||
|
i18n = LANGUAGE,
|
||||||
|
disabled = false,
|
||||||
|
inputClassName = null,
|
||||||
|
containerClassName = null,
|
||||||
|
toggleClassName = null,
|
||||||
|
toggleIcon = undefined,
|
||||||
|
displayFormat = DATE_FORMAT,
|
||||||
|
readOnly = false,
|
||||||
|
minDate = null,
|
||||||
|
maxDate = null,
|
||||||
|
dateLooking = "forward",
|
||||||
|
disabledDates = null,
|
||||||
|
inputId,
|
||||||
|
inputName,
|
||||||
|
startWeekOn = "sun",
|
||||||
|
classNames = undefined,
|
||||||
|
popoverDirection = undefined,
|
||||||
|
mode = "daily",
|
||||||
|
onMark,
|
||||||
|
onLoad,
|
||||||
|
style,
|
||||||
|
}) => {
|
||||||
|
const local = useLocal({ open: false });
|
||||||
|
// Ref
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const calendarContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const arrowRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// State
|
||||||
|
const [firstDate, setFirstDate] = useState<dayjs.Dayjs>(
|
||||||
|
startFrom && dayjs(startFrom).isValid() ? dayjs(startFrom) : dayjs()
|
||||||
|
);
|
||||||
|
const [secondDate, setSecondDate] = useState<dayjs.Dayjs>(
|
||||||
|
nextMonth(firstDate)
|
||||||
|
);
|
||||||
|
const [period, setPeriod] = useState<Period>({
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
});
|
||||||
|
const [dayHover, setDayHover] = useState<string | null>(null);
|
||||||
|
const [inputText, setInputText] = useState<string>("");
|
||||||
|
const [inputRef, setInputRef] = useState(React.createRef<HTMLInputElement>());
|
||||||
|
|
||||||
|
// Custom Hooks use
|
||||||
|
useOnClickOutside(calendarContainerRef, () => {
|
||||||
|
const container = calendarContainerRef.current;
|
||||||
|
if (container) {
|
||||||
|
hideDatepicker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useEffect(() => {}, []);
|
||||||
|
// Functions
|
||||||
|
const hideDatepicker = useCallback(() => {
|
||||||
|
local.open = false;
|
||||||
|
local.render();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/* Start First */
|
||||||
|
const firstGotoDate = useCallback(
|
||||||
|
(date: dayjs.Dayjs) => {
|
||||||
|
const newDate = dayjs(formatDate(date));
|
||||||
|
const reformatDate = dayjs(formatDate(secondDate));
|
||||||
|
if (newDate.isSame(reformatDate) || newDate.isAfter(reformatDate)) {
|
||||||
|
setSecondDate(nextMonth(date));
|
||||||
|
}
|
||||||
|
setFirstDate(date);
|
||||||
|
},
|
||||||
|
[secondDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const previousMonthFirst = useCallback(() => {
|
||||||
|
firstGotoDate(previousMonth(firstDate));
|
||||||
|
}, [firstDate]);
|
||||||
|
|
||||||
|
const nextMonthFirst = useCallback(() => {
|
||||||
|
firstGotoDate(nextMonth(firstDate));
|
||||||
|
}, [firstDate, firstGotoDate]);
|
||||||
|
|
||||||
|
const changeFirstMonth = useCallback(
|
||||||
|
(month: number) => {
|
||||||
|
firstGotoDate(
|
||||||
|
dayjs(`${firstDate.year()}-${month < 10 ? "0" : ""}${month}-01`)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[firstDate, firstGotoDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const changeFirstYear = useCallback(
|
||||||
|
(year: number) => {
|
||||||
|
firstGotoDate(dayjs(`${year}-${firstDate.month() + 1}-01`));
|
||||||
|
},
|
||||||
|
[firstDate, firstGotoDate]
|
||||||
|
);
|
||||||
|
/* End First */
|
||||||
|
|
||||||
|
/* Start Second */
|
||||||
|
const secondGotoDate = useCallback(
|
||||||
|
(date: dayjs.Dayjs) => {
|
||||||
|
const newDate = dayjs(formatDate(date, displayFormat));
|
||||||
|
const reformatDate = dayjs(formatDate(firstDate, displayFormat));
|
||||||
|
if (newDate.isSame(reformatDate) || newDate.isBefore(reformatDate)) {
|
||||||
|
setFirstDate(previousMonth(date));
|
||||||
|
}
|
||||||
|
setSecondDate(date);
|
||||||
|
},
|
||||||
|
[firstDate, displayFormat]
|
||||||
|
);
|
||||||
|
|
||||||
|
const previousMonthSecond = useCallback(() => {
|
||||||
|
secondGotoDate(previousMonth(secondDate));
|
||||||
|
}, [secondDate, secondGotoDate]);
|
||||||
|
|
||||||
|
const nextMonthSecond = useCallback(() => {
|
||||||
|
setSecondDate(nextMonth(secondDate));
|
||||||
|
}, [secondDate]);
|
||||||
|
|
||||||
|
const changeSecondMonth = useCallback(
|
||||||
|
(month: number) => {
|
||||||
|
secondGotoDate(
|
||||||
|
dayjs(`${secondDate.year()}-${month < 10 ? "0" : ""}${month}-01`)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[secondDate, secondGotoDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const changeSecondYear = useCallback(
|
||||||
|
(year: number) => {
|
||||||
|
secondGotoDate(dayjs(`${year}-${secondDate.month() + 1}-01`));
|
||||||
|
},
|
||||||
|
[secondDate, secondGotoDate]
|
||||||
|
);
|
||||||
|
/* End Second */
|
||||||
|
|
||||||
|
// UseEffects & UseLayoutEffect
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
const calendarContainer = calendarContainerRef.current;
|
||||||
|
const arrow = arrowRef.current;
|
||||||
|
|
||||||
|
if (container && calendarContainer && arrow) {
|
||||||
|
const detail = container.getBoundingClientRect();
|
||||||
|
const screenCenter = window.innerWidth / 2;
|
||||||
|
const containerCenter = (detail.right - detail.x) / 2 + detail.x;
|
||||||
|
|
||||||
|
if (containerCenter > screenCenter) {
|
||||||
|
arrow.classList.add("right-0");
|
||||||
|
arrow.classList.add("mr-3.5");
|
||||||
|
calendarContainer.classList.add("right-0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value && value.startDate && value.endDate) {
|
||||||
|
const startDate = dayjs(value.startDate);
|
||||||
|
const endDate = dayjs(value.endDate);
|
||||||
|
const validDate = startDate.isValid() && endDate.isValid();
|
||||||
|
const condition =
|
||||||
|
validDate && (startDate.isSame(endDate) || startDate.isBefore(endDate));
|
||||||
|
if (condition) {
|
||||||
|
setPeriod({
|
||||||
|
start: formatDate(startDate),
|
||||||
|
end: formatDate(endDate),
|
||||||
|
});
|
||||||
|
setInputText(
|
||||||
|
`${formatDate(startDate, displayFormat)}${
|
||||||
|
asSingle
|
||||||
|
? ""
|
||||||
|
: ` ${separator} ${formatDate(endDate, displayFormat)}`
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && value.startDate === null && value.endDate === null) {
|
||||||
|
setPeriod({
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
});
|
||||||
|
setInputText("");
|
||||||
|
}
|
||||||
|
}, [asSingle, value, displayFormat, separator]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (startFrom && dayjs(startFrom).isValid()) {
|
||||||
|
const startDate = value?.startDate;
|
||||||
|
const endDate = value?.endDate;
|
||||||
|
if (startDate && dayjs(startDate).isValid()) {
|
||||||
|
setFirstDate(dayjs(startDate));
|
||||||
|
if (!asSingle) {
|
||||||
|
if (
|
||||||
|
endDate &&
|
||||||
|
dayjs(endDate).isValid() &&
|
||||||
|
dayjs(endDate).startOf("month").isAfter(dayjs(startDate))
|
||||||
|
) {
|
||||||
|
setSecondDate(dayjs(endDate));
|
||||||
|
} else {
|
||||||
|
setSecondDate(nextMonth(dayjs(startDate)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setFirstDate(dayjs(startFrom));
|
||||||
|
setSecondDate(nextMonth(dayjs(startFrom)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [asSingle, startFrom, value]);
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
const safePrimaryColor = useMemo(() => {
|
||||||
|
if (COLORS.includes(primaryColor)) {
|
||||||
|
return primaryColor as ColorKeys;
|
||||||
|
}
|
||||||
|
return DEFAULT_COLOR;
|
||||||
|
}, [primaryColor]);
|
||||||
|
const contextValues = useMemo(() => {
|
||||||
|
return {
|
||||||
|
asSingle,
|
||||||
|
primaryColor: safePrimaryColor,
|
||||||
|
configs,
|
||||||
|
calendarContainer: calendarContainerRef,
|
||||||
|
arrowContainer: arrowRef,
|
||||||
|
hideDatepicker,
|
||||||
|
period,
|
||||||
|
changePeriod: (newPeriod: Period) => setPeriod(newPeriod),
|
||||||
|
dayHover,
|
||||||
|
changeDayHover: (newDay: string | null) => setDayHover(newDay),
|
||||||
|
inputText,
|
||||||
|
changeInputText: (newText: string) => setInputText(newText),
|
||||||
|
updateFirstDate: (newDate: dayjs.Dayjs) => firstGotoDate(newDate),
|
||||||
|
changeDatepickerValue: onChange,
|
||||||
|
showFooter,
|
||||||
|
placeholder,
|
||||||
|
separator,
|
||||||
|
i18n,
|
||||||
|
value,
|
||||||
|
disabled,
|
||||||
|
inputClassName,
|
||||||
|
containerClassName,
|
||||||
|
toggleClassName,
|
||||||
|
toggleIcon,
|
||||||
|
readOnly,
|
||||||
|
displayFormat,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
dateLooking,
|
||||||
|
disabledDates,
|
||||||
|
inputId,
|
||||||
|
inputName,
|
||||||
|
startWeekOn,
|
||||||
|
classNames,
|
||||||
|
onChange,
|
||||||
|
input: inputRef,
|
||||||
|
popoverDirection,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
asSingle,
|
||||||
|
safePrimaryColor,
|
||||||
|
configs,
|
||||||
|
hideDatepicker,
|
||||||
|
period,
|
||||||
|
dayHover,
|
||||||
|
inputText,
|
||||||
|
onChange,
|
||||||
|
showFooter,
|
||||||
|
placeholder,
|
||||||
|
separator,
|
||||||
|
i18n,
|
||||||
|
value,
|
||||||
|
disabled,
|
||||||
|
inputClassName,
|
||||||
|
containerClassName,
|
||||||
|
toggleClassName,
|
||||||
|
toggleIcon,
|
||||||
|
readOnly,
|
||||||
|
displayFormat,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
dateLooking,
|
||||||
|
disabledDates,
|
||||||
|
inputId,
|
||||||
|
inputName,
|
||||||
|
startWeekOn,
|
||||||
|
classNames,
|
||||||
|
inputRef,
|
||||||
|
popoverDirection,
|
||||||
|
firstGotoDate,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const containerClassNameOverload = useMemo(() => {
|
||||||
|
const defaultContainerClassName = "relative w-full text-gray-700";
|
||||||
|
return typeof containerClassName === "function"
|
||||||
|
? containerClassName(defaultContainerClassName)
|
||||||
|
: typeof containerClassName === "string" && containerClassName !== ""
|
||||||
|
? containerClassName
|
||||||
|
: defaultContainerClassName;
|
||||||
|
}, [containerClassName]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatepickerContext.Provider value={contextValues}>
|
||||||
|
<Calendar
|
||||||
|
date={firstDate}
|
||||||
|
onClickPrevious={previousMonthFirst}
|
||||||
|
onClickNext={nextMonthFirst}
|
||||||
|
changeMonth={changeFirstMonth}
|
||||||
|
changeYear={changeFirstYear}
|
||||||
|
mode={mode}
|
||||||
|
minDate={minDate}
|
||||||
|
maxDate={maxDate}
|
||||||
|
onMark={onMark}
|
||||||
|
style={style}
|
||||||
|
onLoad={onLoad}
|
||||||
|
/>
|
||||||
|
</DatepickerContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalenderFull;
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import isBetween from "dayjs/plugin/isBetween";
|
import isBetween from "dayjs/plugin/isBetween";
|
||||||
import React, { useCallback, useContext } from "react";
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import { BG_COLOR, TEXT_COLOR } from "../../constants";
|
import { BG_COLOR, TEXT_COLOR } from "../../constants";
|
||||||
import DatepickerContext from "../../contexts/DatepickerContext";
|
import DatepickerContext from "../../contexts/DatepickerContext";
|
||||||
|
|
@ -11,6 +17,8 @@ import {
|
||||||
classNames as cn,
|
classNames as cn,
|
||||||
} from "../../helpers";
|
} from "../../helpers";
|
||||||
import { Period } from "../../types";
|
import { Period } from "../../types";
|
||||||
|
import get from "lodash.get";
|
||||||
|
import { getNumber } from "@/lib/utils/getNumber";
|
||||||
|
|
||||||
dayjs.extend(isBetween);
|
dayjs.extend(isBetween);
|
||||||
|
|
||||||
|
|
@ -26,16 +34,24 @@ interface Props {
|
||||||
onClickPreviousDays: (day: number) => void;
|
onClickPreviousDays: (day: number) => void;
|
||||||
onClickDay: (day: number) => void;
|
onClickDay: (day: number) => void;
|
||||||
onClickNextDays: (day: number) => void;
|
onClickNextDays: (day: number) => void;
|
||||||
onIcon?: (day: number, date: Date) => any;
|
onIcon?: (day: number, date: Date, data?: any) => any;
|
||||||
|
style?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Days: React.FC<Props> = ({
|
const Days: React.FC<Props> = ({
|
||||||
calendarData,
|
calendarData,
|
||||||
onClickPreviousDays,
|
onClickPreviousDays,
|
||||||
onClickDay,
|
onClickDay,
|
||||||
onClickNextDays,
|
onClickNextDays,
|
||||||
onIcon,
|
onIcon,
|
||||||
|
style,
|
||||||
}) => {
|
}) => {
|
||||||
|
// Ref
|
||||||
|
const calendarRef = useRef(null);
|
||||||
|
const markRef = useRef(null);
|
||||||
|
const [height, setHeight] = useState(0);
|
||||||
|
const [heightItem, setHeightItem] = useState(0);
|
||||||
|
const [maxItem, setMaxItem] = useState(0);
|
||||||
|
const [width, setWidth] = useState(0);
|
||||||
// Contexts
|
// Contexts
|
||||||
const {
|
const {
|
||||||
primaryColor,
|
primaryColor,
|
||||||
|
|
@ -76,17 +92,13 @@ const Days: React.FC<Props> = ({
|
||||||
) {
|
) {
|
||||||
className = ` ${BG_COLOR["500"][primaryColor]} text-white font-medium rounded-full`;
|
className = ` ${BG_COLOR["500"][primaryColor]} text-white font-medium rounded-full`;
|
||||||
} else if (dayjs(fullDay).isSame(period.start)) {
|
} else if (dayjs(fullDay).isSame(period.start)) {
|
||||||
className = ` ${
|
className = ` ${BG_COLOR["500"][primaryColor]} text-white font-medium ${
|
||||||
BG_COLOR["500"][primaryColor]
|
|
||||||
} text-white font-medium ${
|
|
||||||
dayjs(fullDay).isSame(dayHover) && !period.end
|
dayjs(fullDay).isSame(dayHover) && !period.end
|
||||||
? "rounded-full"
|
? "rounded-full"
|
||||||
: "rounded-l-full"
|
: "rounded-l-full"
|
||||||
}`;
|
}`;
|
||||||
} else if (dayjs(fullDay).isSame(period.end)) {
|
} else if (dayjs(fullDay).isSame(period.end)) {
|
||||||
className = ` ${
|
className = ` ${BG_COLOR["500"][primaryColor]} text-white font-medium ${
|
||||||
BG_COLOR["500"][primaryColor]
|
|
||||||
} text-white font-medium ${
|
|
||||||
dayjs(fullDay).isSame(dayHover) && !period.start
|
dayjs(fullDay).isSame(dayHover) && !period.start
|
||||||
? "rounded-full"
|
? "rounded-full"
|
||||||
: "rounded-r-full"
|
: "rounded-r-full"
|
||||||
|
|
@ -241,13 +253,16 @@ const Days: React.FC<Props> = ({
|
||||||
|
|
||||||
const buttonClass = useCallback(
|
const buttonClass = useCallback(
|
||||||
(day: number, type: "current" | "next" | "previous") => {
|
(day: number, type: "current" | "next" | "previous") => {
|
||||||
const baseClass =
|
let baseClass = `calender-day flex items-center justify-center ${
|
||||||
"flex items-center justify-center w-10 h-10 relative";
|
style === "custom" ? " w-6 h-6 m-1" : "w-12 h-12 lg:w-10 lg:h-10"
|
||||||
|
} relative`;
|
||||||
if (type === "current") {
|
if (type === "current") {
|
||||||
return cn(
|
return cn(
|
||||||
baseClass,
|
baseClass,
|
||||||
!activeDateData(day).active
|
!activeDateData(day).active
|
||||||
? hoverClassByDay(day)
|
? hoverClassByDay(day)
|
||||||
|
: style === "custom"
|
||||||
|
? ""
|
||||||
: activeDateData(day).className,
|
: activeDateData(day).className,
|
||||||
isDateDisabled(day, type) && "text-gray-400 cursor-not-allowed"
|
isDateDisabled(day, type) && "text-gray-400 cursor-not-allowed"
|
||||||
);
|
);
|
||||||
|
|
@ -400,65 +415,222 @@ const Days: React.FC<Props> = ({
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
const res = new Date(fullDay);
|
const res = new Date(fullDay);
|
||||||
return typeof onIcon === "function" ? onIcon(day, res) : null;
|
return typeof onIcon === "function"
|
||||||
|
? onIcon(day, res, {
|
||||||
|
ref: calendarRef,
|
||||||
|
height,
|
||||||
|
maxItem,
|
||||||
|
width,
|
||||||
|
heightItem,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
};
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (calendarRef?.current && markRef?.current) {
|
||||||
|
const card = getNumber(get(calendarRef, "current.clientWidth"));
|
||||||
|
const cardHeight = getNumber(get(markRef, "current.clientHeight"));
|
||||||
|
const heightItem = 20; // perkiraan
|
||||||
|
setWidth(card - 2);
|
||||||
|
setHeight(cardHeight);
|
||||||
|
setMaxItem(Math.floor(cardHeight / heightItem));
|
||||||
|
setHeightItem(20);
|
||||||
|
// setMaxItem
|
||||||
|
const day = 3;
|
||||||
|
const fullwidth = card * 7;
|
||||||
|
const percent = (7 / 3) * 100;
|
||||||
|
}
|
||||||
|
}, [calendarRef.current, markRef.current]);
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-7 gap-y-0.5 my-1">
|
<div
|
||||||
|
className={cx(
|
||||||
|
"calender-days grid grid-cols-7 ",
|
||||||
|
style === "custom" ? "" : " my-1 gap-y-0.5",
|
||||||
|
css`
|
||||||
|
z-index: 0;
|
||||||
|
.calender-grid {
|
||||||
|
// aspect-ratio: 1 / 1;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
{calendarData.days.previous.map((item, index) => (
|
{calendarData.days.previous.map((item, index) => (
|
||||||
<button
|
<div
|
||||||
type="button"
|
key={"prev_" + index}
|
||||||
key={index}
|
className={cx(
|
||||||
disabled={isDateDisabled(item, "previous")}
|
"calender-grid flex flex-row",
|
||||||
className={`${buttonClass(item, "previous")}`}
|
style === "custom"
|
||||||
onClick={() => handleClickDay(item, "previous")}
|
? "border-gray-200 hover:bg-gray-100 cursor-pointer"
|
||||||
onMouseOver={() => {
|
: ""
|
||||||
hoverDay(item, "previous");
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (style === "custom") handleClickDay(item, "previous");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<div className="flex flex-col flex-grow calender-day-wrap">
|
||||||
{item}
|
{style === "custom" ? (
|
||||||
{load_marker(item, "previous")}
|
<>
|
||||||
</span>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
key={index}
|
||||||
|
disabled={isDateDisabled(item, "previous")}
|
||||||
|
className={`${buttonClass(item, "previous")}`}
|
||||||
|
onMouseOver={() => {
|
||||||
|
hoverDay(item, "previous");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative">{item}</span>
|
||||||
|
</button>
|
||||||
|
<div className="flex flex-grow relative">
|
||||||
|
{load_marker(item, "previous")}
|
||||||
|
{/*
|
||||||
|
{index === 1 && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"hover:bg-gray-200 font-bold text-sm text-black px-2 absolute top-[27px] left-0 w-[196px] rounded-md",
|
||||||
|
css`
|
||||||
|
z-index: 1;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
1 more
|
||||||
|
</div>
|
||||||
|
)} */}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={index}
|
||||||
|
disabled={isDateDisabled(item, "previous")}
|
||||||
|
className={`${buttonClass(item, "previous")}`}
|
||||||
|
onClick={() => handleClickDay(item, "previous")}
|
||||||
|
onMouseOver={() => {
|
||||||
|
hoverDay(item, "previous");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative">
|
||||||
|
{item}
|
||||||
|
{load_marker(item, "previous")}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{calendarData.days.current.map((item, index) => (
|
{calendarData.days.current.map((item, index) => (
|
||||||
<button
|
<div
|
||||||
type="button"
|
key={"current_" + index}
|
||||||
key={index}
|
ref={index === 0 ? calendarRef : null}
|
||||||
disabled={isDateDisabled(item, "current")}
|
|
||||||
className={cx(
|
className={cx(
|
||||||
`${buttonClass(item, "current")}`,
|
"calender-grid flex flex-row",
|
||||||
item === 1 && "highlight"
|
style === "custom"
|
||||||
|
? activeDateData(item).active
|
||||||
|
? "bg-blue-200/75 ring-1 cursor-pointer border-gray-200"
|
||||||
|
: "hover:bg-gray-50 cursor-pointer border-gray-200 bg-white"
|
||||||
|
: ""
|
||||||
)}
|
)}
|
||||||
onClick={() => handleClickDay(item, "current")}
|
onClick={() => {
|
||||||
onMouseOver={() => {
|
if (style === "custom") handleClickDay(item, "current");
|
||||||
hoverDay(item, "current");
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<div className="flex flex-col flex-grow calender-day-wrap">
|
||||||
{item}
|
{style === "custom" ? (
|
||||||
{load_marker(item, "current")}
|
<>
|
||||||
</span>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
key={index}
|
||||||
|
disabled={isDateDisabled(item, "current")}
|
||||||
|
className={`${buttonClass(item, "current")}`}
|
||||||
|
// onClick={() => handleClickDay(item, "current")}
|
||||||
|
onMouseOver={() => {
|
||||||
|
hoverDay(item, "current");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative">{item}</span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className="flex flex-grow relative "
|
||||||
|
ref={index === 0 ? markRef : null}
|
||||||
|
>
|
||||||
|
{load_marker(item, "current")}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={index}
|
||||||
|
disabled={isDateDisabled(item, "current")}
|
||||||
|
className={`${buttonClass(item, "current")}`}
|
||||||
|
onClick={() => handleClickDay(item, "current")}
|
||||||
|
onMouseOver={() => {
|
||||||
|
hoverDay(item, "current");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative">
|
||||||
|
{item}
|
||||||
|
{load_marker(item, "current")}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{calendarData.days.next.map((item, index) => (
|
{calendarData.days.next.map((item, index) => (
|
||||||
<button
|
<div
|
||||||
type="button"
|
key={"next_" + index}
|
||||||
key={index}
|
className={cx(
|
||||||
disabled={isDateDisabled(item, "next")}
|
"calender-grid flex flex-row ",
|
||||||
className={`${buttonClass(item, "next")}`}
|
style === "custom"
|
||||||
onClick={() => handleClickDay(item, "next")}
|
? "hover:bg-gray-100 cursor-pointer border-gray-200"
|
||||||
onMouseOver={() => {
|
: ""
|
||||||
hoverDay(item, "next");
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (style === "custom") handleClickDay(item, "next");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<div className="flex flex-col flex-grow calender-day-wrap">
|
||||||
{item}
|
{style === "custom" ? (
|
||||||
{load_marker(item, "next")}
|
<>
|
||||||
</span>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
key={index}
|
||||||
|
disabled={isDateDisabled(item, "next")}
|
||||||
|
className={`${buttonClass(item, "next")}`}
|
||||||
|
onMouseOver={() => {
|
||||||
|
hoverDay(item, "next");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative">{item}</span>
|
||||||
|
</button>
|
||||||
|
<div>{load_marker(item, "next")}</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={index}
|
||||||
|
disabled={isDateDisabled(item, "next")}
|
||||||
|
className={`${buttonClass(item, "next")}`}
|
||||||
|
onClick={() => handleClickDay(item, "next")}
|
||||||
|
onMouseOver={() => {
|
||||||
|
hoverDay(item, "next");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative">
|
||||||
|
{item}
|
||||||
|
{load_marker(item, "next")}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,19 @@ import { RoundedButton } from "../utils";
|
||||||
interface Props {
|
interface Props {
|
||||||
currentMonth: number;
|
currentMonth: number;
|
||||||
clickMonth: (month: number) => void;
|
clickMonth: (month: number) => void;
|
||||||
|
style?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Months: React.FC<Props> = ({ currentMonth, clickMonth }) => {
|
const Months: React.FC<Props> = ({ currentMonth, clickMonth, style }) => {
|
||||||
const { i18n } = useContext(DatepickerContext);
|
const { i18n } = useContext(DatepickerContext);
|
||||||
loadLanguageModule(i18n);
|
loadLanguageModule(i18n);
|
||||||
return (
|
return (
|
||||||
<div className={"w-full grid grid-cols-2 gap-2 mt-2"}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
"w-full grid gap-2 mt-2",
|
||||||
|
style === "custom" ? "uppercase grid-cols-2 p-4" : "grid-cols-2"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{MONTHS.map((item) => (
|
{MONTHS.map((item) => (
|
||||||
<RoundedButton
|
<RoundedButton
|
||||||
key={item}
|
key={item}
|
||||||
|
|
@ -24,8 +30,15 @@ const Months: React.FC<Props> = ({ currentMonth, clickMonth }) => {
|
||||||
clickMonth(item);
|
clickMonth(item);
|
||||||
}}
|
}}
|
||||||
active={currentMonth === item}
|
active={currentMonth === item}
|
||||||
|
style={style}
|
||||||
>
|
>
|
||||||
<>{dayjs(`2022-${item}-01`).locale(i18n).format("MMM")}</>
|
{style === "custom" ? (
|
||||||
|
<div className="px-2 py-1">
|
||||||
|
{dayjs(`2022-${item}-01`).locale(i18n).format("MMMM")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>{dayjs(`2022-${item}-01`).locale(i18n).format("MMM")}</>
|
||||||
|
)}
|
||||||
</RoundedButton>
|
</RoundedButton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ import React, { useContext, useMemo } from "react";
|
||||||
import { DAYS } from "../../constants";
|
import { DAYS } from "../../constants";
|
||||||
import DatepickerContext from "../../contexts/DatepickerContext";
|
import DatepickerContext from "../../contexts/DatepickerContext";
|
||||||
import { loadLanguageModule, shortString, ucFirst } from "../../helpers";
|
import { loadLanguageModule, shortString, ucFirst } from "../../helpers";
|
||||||
|
interface Props {
|
||||||
const Week: React.FC = () => {
|
style?: string;
|
||||||
|
}
|
||||||
|
const Week: React.FC<Props> = ({ style }) => {
|
||||||
const { i18n, startWeekOn } = useContext(DatepickerContext);
|
const { i18n, startWeekOn } = useContext(DatepickerContext);
|
||||||
loadLanguageModule(i18n);
|
loadLanguageModule(i18n);
|
||||||
const startDateModifier = useMemo(() => {
|
const startDateModifier = useMemo(() => {
|
||||||
|
|
@ -33,11 +35,25 @@ const Week: React.FC = () => {
|
||||||
}, [startWeekOn]);
|
}, [startWeekOn]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" grid grid-cols-7 border-b border-gray-300 dark:border-gray-700 py-2">
|
<div
|
||||||
|
className={cx(
|
||||||
|
" grid grid-cols-7 border-b dark:border-gray-700",
|
||||||
|
style === "custom"
|
||||||
|
? "sticky top-0 bg-white z-99 border-gray-200"
|
||||||
|
: "border-gray-300 py-2",
|
||||||
|
style === "custom" &&
|
||||||
|
css`
|
||||||
|
z-index: 99;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
{DAYS.map((item) => (
|
{DAYS.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item}
|
key={item}
|
||||||
className="tracking-wide text-gray-500 text-center"
|
className={cx(
|
||||||
|
"tracking-wide text-gray-500 text-center",
|
||||||
|
style === "custom" && " border-r border-gray-200 py-2"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{ucFirst(
|
{ucFirst(
|
||||||
shortString(
|
shortString(
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ interface Props {
|
||||||
minYear: number | null;
|
minYear: number | null;
|
||||||
maxYear: number | null;
|
maxYear: number | null;
|
||||||
clickYear: (data: number) => void;
|
clickYear: (data: number) => void;
|
||||||
|
style?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Years: React.FC<Props> = ({
|
const Years: React.FC<Props> = ({
|
||||||
|
|
@ -19,6 +20,7 @@ const Years: React.FC<Props> = ({
|
||||||
minYear,
|
minYear,
|
||||||
maxYear,
|
maxYear,
|
||||||
clickYear,
|
clickYear,
|
||||||
|
style,
|
||||||
}) => {
|
}) => {
|
||||||
const { dateLooking } = useContext(DatepickerContext);
|
const { dateLooking } = useContext(DatepickerContext);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import Week from "./Week";
|
||||||
import Years from "./Years";
|
import Years from "./Years";
|
||||||
|
|
||||||
import { DateType } from "../../types";
|
import { DateType } from "../../types";
|
||||||
|
import { Popover } from "@/lib/components/Popover/Popover";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
date: dayjs.Dayjs;
|
date: dayjs.Dayjs;
|
||||||
|
|
@ -44,7 +45,9 @@ interface Props {
|
||||||
changeMonth: (month: number) => void;
|
changeMonth: (month: number) => void;
|
||||||
changeYear: (year: number) => void;
|
changeYear: (year: number) => void;
|
||||||
mode?: "monthly" | "daily";
|
mode?: "monthly" | "daily";
|
||||||
onMark?: (day: number, date: Date) => any;
|
onMark?: (day: number, date: Date, data?: any) => any;
|
||||||
|
style?: "custom" | "prasi";
|
||||||
|
onLoad?: (e?: any) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Calendar: React.FC<Props> = ({
|
const Calendar: React.FC<Props> = ({
|
||||||
|
|
@ -57,6 +60,8 @@ const Calendar: React.FC<Props> = ({
|
||||||
changeYear,
|
changeYear,
|
||||||
onMark,
|
onMark,
|
||||||
mode = "daily",
|
mode = "daily",
|
||||||
|
style = "prasi",
|
||||||
|
onLoad,
|
||||||
}) => {
|
}) => {
|
||||||
// Contexts
|
// Contexts
|
||||||
const {
|
const {
|
||||||
|
|
@ -77,6 +82,8 @@ const Calendar: React.FC<Props> = ({
|
||||||
const [showMonths, setShowMonths] = useState(false);
|
const [showMonths, setShowMonths] = useState(false);
|
||||||
const [showYears, setShowYears] = useState(false);
|
const [showYears, setShowYears] = useState(false);
|
||||||
const [year, setYear] = useState(date.year());
|
const [year, setYear] = useState(date.year());
|
||||||
|
const [openPopover, setOpenPopover] = useState(false);
|
||||||
|
const [openPopoverYear, setOpenPopoverYear] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === "monthly") {
|
if (mode === "monthly") {
|
||||||
setShowMonths(true);
|
setShowMonths(true);
|
||||||
|
|
@ -90,7 +97,12 @@ const Calendar: React.FC<Props> = ({
|
||||||
getNumberOfDay(getFirstDayInMonth(date).ddd, startWeekOn)
|
getNumberOfDay(getFirstDayInMonth(date).ddd, startWeekOn)
|
||||||
);
|
);
|
||||||
}, [date, startWeekOn]);
|
}, [date, startWeekOn]);
|
||||||
|
const previousDate = useCallback(() => {
|
||||||
|
const day = getLastDaysInMonth(
|
||||||
|
previousMonth(date),
|
||||||
|
getNumberOfDay(getFirstDayInMonth(date).ddd, startWeekOn)
|
||||||
|
);
|
||||||
|
}, [date, startWeekOn]);
|
||||||
const current = useCallback(() => {
|
const current = useCallback(() => {
|
||||||
return getDaysInMonth(formatDate(date));
|
return getDaysInMonth(formatDate(date));
|
||||||
}, [date]);
|
}, [date]);
|
||||||
|
|
@ -245,17 +257,79 @@ const Calendar: React.FC<Props> = ({
|
||||||
setYear(date.year());
|
setYear(date.year());
|
||||||
}, [date]);
|
}, [date]);
|
||||||
|
|
||||||
|
const getMonth = (month?: string) => {
|
||||||
|
const value: any = date;
|
||||||
|
const currentDate: any = new Date(value);
|
||||||
|
const previousMonthDate = new Date(currentDate);
|
||||||
|
previousMonthDate.setDate(1);
|
||||||
|
switch (month) {
|
||||||
|
case "before":
|
||||||
|
previousMonthDate.setMonth(currentDate.getMonth() - 1);
|
||||||
|
break;
|
||||||
|
case "after":
|
||||||
|
previousMonthDate.setMonth(currentDate.getMonth() + 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return previousMonthDate;
|
||||||
|
};
|
||||||
// Variables
|
// Variables
|
||||||
const calendarData = useMemo(() => {
|
const calendarData = useMemo(() => {
|
||||||
return {
|
const data = {
|
||||||
|
previous: previous(),
|
||||||
|
current: current(),
|
||||||
|
next: next(),
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
date: date,
|
date: date,
|
||||||
days: {
|
days: data,
|
||||||
previous: previous(),
|
time: {
|
||||||
current: current(),
|
previous: data?.previous?.length
|
||||||
next: next(),
|
? data.previous.map((e) => {
|
||||||
|
return new Date(
|
||||||
|
getMonth("before").getFullYear(),
|
||||||
|
getMonth("before").getMonth(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
current: data?.current?.length
|
||||||
|
? data.current.map((e) => {
|
||||||
|
return new Date(
|
||||||
|
getMonth().getFullYear(),
|
||||||
|
getMonth().getMonth(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
next: data?.next?.length
|
||||||
|
? data.next.map((e) => {
|
||||||
|
return new Date(
|
||||||
|
getMonth("after").getFullYear(),
|
||||||
|
getMonth("after").getMonth(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
return result;
|
||||||
}, [current, date, next, previous]);
|
}, [current, date, next, previous]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof onLoad === "function") {
|
||||||
|
const run = async () => {
|
||||||
|
if (typeof onLoad === "function") {
|
||||||
|
const param = dayjs(formatDate(date)).toDate();
|
||||||
|
await onLoad({
|
||||||
|
date: param,
|
||||||
|
calender: calendarData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}, [calendarData, date]);
|
||||||
const minYear = React.useMemo(
|
const minYear = React.useMemo(
|
||||||
() => (minDate && dayjs(minDate).isValid() ? dayjs(minDate).year() : null),
|
() => (minDate && dayjs(minDate).isValid() ? dayjs(minDate).year() : null),
|
||||||
[minDate]
|
[minDate]
|
||||||
|
|
@ -264,88 +338,131 @@ const Calendar: React.FC<Props> = ({
|
||||||
() => (maxDate && dayjs(maxDate).isValid() ? dayjs(maxDate).year() : null),
|
() => (maxDate && dayjs(maxDate).isValid() ? dayjs(maxDate).year() : null),
|
||||||
[maxDate]
|
[maxDate]
|
||||||
);
|
);
|
||||||
|
const isCustom = style === "custom";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full md:w-[296px] md:min-w-[296px]">
|
<div
|
||||||
|
className={cx(
|
||||||
|
"w-full md:w-[296px] md:min-w-[296px] calender",
|
||||||
|
isCustom && "flex-grow"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"flex items-stretch space-x-1.5 px-2 py-1.5",
|
"flex items-stretch ",
|
||||||
|
isCustom ? "" : "space-x-1.5 px-2 py-1.5 flex-col",
|
||||||
css`
|
css`
|
||||||
border-bottom: 1px solid #d1d5db;
|
border-bottom: 1px solid #d1d5db;
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!showMonths && !showYears && (
|
{style === "custom" ? (
|
||||||
<div className="flex-none flex flex-row items-center">
|
<div className="flex flex-row items-center px-2 py-2 justify-between w-full">
|
||||||
<RoundedButton roundedFull={true} onClick={onClickPrevious}>
|
<div className="flex flex-row gap-x-2 items-center">
|
||||||
<ChevronLeftIcon className="h-5 w-5" />
|
<div className="flex flex-row gap-x-2">
|
||||||
</RoundedButton>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClickPrevious}
|
||||||
|
className="flex items-center justify-center rounded-l-md py-2 pl-3 pr-4 text-gray-400 hover:text-gray-500 focus:relative md:w-9 md:px-2 md:hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClickNext}
|
||||||
|
className="flex items-center justify-center rounded-r-md py-2 pl-4 pr-3 text-gray-400 hover:text-gray-500 focus:relative md:w-9 md:px-2 md:hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<ChevronRightIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="text-base font-semibold text-gray-900 capitalize flex flex-row">
|
||||||
|
{calendarData.date.locale(i18n).format("MMMM")}{" "}
|
||||||
|
{calendarData.date.year()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : (
|
||||||
|
<div>
|
||||||
|
{!showMonths && !showYears && (
|
||||||
|
<div className="flex-none">
|
||||||
|
<RoundedButton roundedFull={true} onClick={onClickPrevious}>
|
||||||
|
<ChevronLeftIcon className="h-5 w-5" />
|
||||||
|
</RoundedButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{showYears && (
|
{showYears && (
|
||||||
<div className="flex-none flex flex-row items-center">
|
<div className="flex-none">
|
||||||
<RoundedButton
|
<RoundedButton
|
||||||
roundedFull={true}
|
roundedFull={true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setYear(year - 12);
|
setYear(year - 12);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DoubleChevronLeftIcon className="h-5 w-5" />
|
<DoubleChevronLeftIcon className="h-5 w-5" />
|
||||||
</RoundedButton>
|
</RoundedButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className=" flex flex-1 items-stretch space-x-1.5">
|
<div className=" flex flex-1 items-stretch space-x-1.5">
|
||||||
<div className="w-1/2 flex items-stretch">
|
<div className="w-1/2 flex items-stretch">
|
||||||
<RoundedButton
|
<RoundedButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowMonths(!showMonths);
|
setShowMonths(!showMonths);
|
||||||
hideYears();
|
hideYears();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<>{calendarData.date.locale(i18n).format("MMM")}</>
|
<>{calendarData.date.locale(i18n).format("MMM")}</>
|
||||||
</RoundedButton>
|
</RoundedButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-1/2 flex items-stretch">
|
<div className="w-1/2 flex items-stretch">
|
||||||
<RoundedButton
|
<RoundedButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowYears(!showYears);
|
setShowYears(!showYears);
|
||||||
hideMonths();
|
hideMonths();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="">{calendarData.date.year()}</div>
|
<div className="py-2">{calendarData.date.year()}</div>
|
||||||
</RoundedButton>
|
</RoundedButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showYears && (
|
{showYears && (
|
||||||
<div className="flex-none flex flex-row items-center">
|
<div className="flex-none">
|
||||||
<RoundedButton
|
<RoundedButton
|
||||||
roundedFull={true}
|
roundedFull={true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setYear(year + 12);
|
setYear(year + 12);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DoubleChevronRightIcon className="h-5 w-5" />
|
<DoubleChevronRightIcon className="h-5 w-5" />
|
||||||
</RoundedButton>
|
</RoundedButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showMonths && !showYears && (
|
{!showMonths && !showYears && (
|
||||||
<div className="flex-none flex flex-row items-center" >
|
<div className="flex-none">
|
||||||
<RoundedButton roundedFull={true} onClick={onClickNext}>
|
<RoundedButton roundedFull={true} onClick={onClickNext}>
|
||||||
<ChevronRightIcon className="h-5 w-5" />
|
<ChevronRightIcon className="h-5 w-5" />
|
||||||
</RoundedButton>
|
</RoundedButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={cx("mt-0.5 min-h-[285px]")}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
isCustom ? "flex-grow" : "min-h-[285px]",
|
||||||
|
"mt-0.5 calender-body"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{showMonths && (
|
{showMonths && (
|
||||||
<Months
|
<Months
|
||||||
currentMonth={calendarData.date.month() + 1}
|
currentMonth={calendarData.date.month() + 1}
|
||||||
clickMonth={clickMonth}
|
clickMonth={clickMonth}
|
||||||
|
style={style}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -361,27 +478,19 @@ const Calendar: React.FC<Props> = ({
|
||||||
|
|
||||||
{!showMonths && !showYears && (
|
{!showMonths && !showYears && (
|
||||||
<>
|
<>
|
||||||
<Week />
|
<Week style={style} />
|
||||||
|
|
||||||
<Days
|
<Days
|
||||||
calendarData={calendarData}
|
calendarData={calendarData}
|
||||||
onClickPreviousDays={clickPreviousDays}
|
onClickPreviousDays={clickPreviousDays}
|
||||||
onClickDay={clickDay}
|
onClickDay={clickDay}
|
||||||
onClickNextDays={clickNextDays}
|
onClickNextDays={clickNextDays}
|
||||||
onIcon={(day, date) => {
|
style={style}
|
||||||
if(typeof onMark === "function"){
|
onIcon={(day, date, data) => {
|
||||||
return onMark(day, date)
|
if (typeof onMark === "function") {
|
||||||
|
return onMark(day, date, data);
|
||||||
}
|
}
|
||||||
return <></>
|
return <></>;
|
||||||
if (new Date().getDate() === day)
|
|
||||||
return (
|
|
||||||
<div className="absolute inset-y-0 left-0 -translate-y-1/2 -translate-x-1/2">
|
|
||||||
<div className="w-full h-full flex flex-row items-center justif-center px-0.5">
|
|
||||||
!
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return <></>
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ interface Button {
|
||||||
roundedFull?: boolean;
|
roundedFull?: boolean;
|
||||||
padding?: string;
|
padding?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
style?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DateIcon: React.FC<IconProps> = ({ className = "w-6 h-6" }) => {
|
export const DateIcon: React.FC<IconProps> = ({ className = "w-6 h-6" }) => {
|
||||||
|
|
@ -205,27 +207,28 @@ export const RoundedButton: React.FC<Button> = ({
|
||||||
onClick,
|
onClick,
|
||||||
disabled,
|
disabled,
|
||||||
roundedFull = false,
|
roundedFull = false,
|
||||||
padding = "py-[0.55rem]",
|
padding = "",
|
||||||
active = false,
|
active = false,
|
||||||
|
style,
|
||||||
|
className = "rounded-full",
|
||||||
}) => {
|
}) => {
|
||||||
// Contexts
|
// Contexts
|
||||||
const { primaryColor } = useContext(DatepickerContext);
|
const { primaryColor } = useContext(DatepickerContext);
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
const getClassName = useCallback(() => {
|
const getClassName = useCallback(() => {
|
||||||
const darkClass =
|
const darkClass = "";
|
||||||
"";
|
const activeClass = active ? "font-semibold bg-gray-50 " : "";
|
||||||
const activeClass = active
|
|
||||||
? "font-semibold bg-gray-50 "
|
|
||||||
: "";
|
|
||||||
const defaultClass = !roundedFull
|
const defaultClass = !roundedFull
|
||||||
? `w-full tracking-wide ${darkClass} ${activeClass} transition-all duration-300 ${padding} uppercase hover:bg-gray-100 rounded-md focus:ring-1`
|
? `w-full tracking-wide ${darkClass} ${activeClass} transition-all duration-300 ${
|
||||||
: `${darkClass} ${activeClass} transition-all duration-300 hover:bg-gray-100 rounded-full p-[0.45rem] focus:ring-1`;
|
style === "custom" ? "px-2" : "uppercase p-[0.45rem] py-[0.55rem]"
|
||||||
|
} hover:bg-gray-100 focus:ring-1`
|
||||||
|
: `${darkClass} ${activeClass} transition-all duration-300 hover:bg-gray-100 focus:ring-1`;
|
||||||
const buttonFocusColor =
|
const buttonFocusColor =
|
||||||
BUTTON_COLOR.focus[primaryColor as keyof typeof BUTTON_COLOR.focus];
|
BUTTON_COLOR.focus[primaryColor as keyof typeof BUTTON_COLOR.focus];
|
||||||
const disabledClass = disabled ? "line-through" : "";
|
const disabledClass = disabled ? "line-through" : "";
|
||||||
|
|
||||||
return `${defaultClass} ${buttonFocusColor} ${disabledClass}`;
|
return `${defaultClass} ${buttonFocusColor} ${disabledClass} ${className}`;
|
||||||
}, [disabled, padding, primaryColor, roundedFull, active]);
|
}, [disabled, padding, primaryColor, roundedFull, active]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,21 @@ export function getFirstElementsInArray(array: number[] = [], size = 0) {
|
||||||
return array.slice(0, size);
|
return array.slice(0, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLastElementsDateInArray(array: any[] = [], size = 0) {
|
||||||
|
const result: any[] = [];
|
||||||
|
if (Array.isArray(array) && size > 0) {
|
||||||
|
if (size >= array.length) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
let y = array.length - 1;
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
result.push(array[y]);
|
||||||
|
y--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.reverse();
|
||||||
|
}
|
||||||
export function getLastElementsInArray(array: number[] = [], size = 0) {
|
export function getLastElementsInArray(array: number[] = [], size = 0) {
|
||||||
const result: number[] = [];
|
const result: number[] = [];
|
||||||
if (Array.isArray(array) && size > 0) {
|
if (Array.isArray(array) && size > 0) {
|
||||||
|
|
@ -194,7 +209,9 @@ export function getNumberOfDay(
|
||||||
export function getLastDaysInMonth(date: dayjs.Dayjs | string, size = 0) {
|
export function getLastDaysInMonth(date: dayjs.Dayjs | string, size = 0) {
|
||||||
return getLastElementsInArray(getDaysInMonth(date), size);
|
return getLastElementsInArray(getDaysInMonth(date), size);
|
||||||
}
|
}
|
||||||
|
export function getLastDateInMonth(date: dayjs.Dayjs | string, size = 0) {
|
||||||
|
return getLastElementsInArray(getDaysInMonth(date), size);
|
||||||
|
}
|
||||||
export function getFirstDaysInMonth(date: string | dayjs.Dayjs, size = 0) {
|
export function getFirstDaysInMonth(date: string | dayjs.Dayjs, size = 0) {
|
||||||
return getFirstElementsInArray(getDaysInMonth(date), size);
|
return getFirstElementsInArray(getDaysInMonth(date), size);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,95 +3,98 @@ import React from "react";
|
||||||
import { COLORS } from "../constants";
|
import { COLORS } from "../constants";
|
||||||
|
|
||||||
export interface Period {
|
export interface Period {
|
||||||
start: string | null;
|
start: string | null;
|
||||||
end: string | null;
|
end: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomShortcuts {
|
interface CustomShortcuts {
|
||||||
[key: string]: ShortcutsItem;
|
[key: string]: ShortcutsItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DefaultShortcuts {
|
interface DefaultShortcuts {
|
||||||
today?: string;
|
today?: string;
|
||||||
yesterday?: string;
|
yesterday?: string;
|
||||||
past?: (period: number) => string;
|
past?: (period: number) => string;
|
||||||
currentMonth?: string;
|
currentMonth?: string;
|
||||||
pastMonth?: string;
|
pastMonth?: string;
|
||||||
}
|
}
|
||||||
export interface Configs {
|
export interface Configs {
|
||||||
shortcuts?: DefaultShortcuts | CustomShortcuts;
|
shortcuts?: DefaultShortcuts | CustomShortcuts;
|
||||||
footer?: {
|
footer?: {
|
||||||
cancel?: string;
|
cancel?: string;
|
||||||
apply?: string;
|
apply?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShortcutsItem {
|
export interface ShortcutsItem {
|
||||||
text: string;
|
text: string;
|
||||||
daysNumber?: number;
|
daysNumber?: number;
|
||||||
period: {
|
period: {
|
||||||
start: Date | string;
|
start: Date | string;
|
||||||
end: Date | string;
|
end: Date | string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DateType = string | null | Date;
|
export type DateType = string | null | Date;
|
||||||
|
|
||||||
export type DateRangeType = {
|
export type DateRangeType = {
|
||||||
startDate: DateType;
|
startDate: DateType;
|
||||||
endDate: DateType;
|
endDate: DateType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DateValueType = DateRangeType | null;
|
export type DateValueType = DateRangeType | null;
|
||||||
|
|
||||||
export type ClassNamesTypeProp = {
|
export type ClassNamesTypeProp = {
|
||||||
container?: (p?: object | null | undefined) => string | undefined;
|
container?: (p?: object | null | undefined) => string | undefined;
|
||||||
input?: (p?: object | null | undefined) => string | undefined;
|
input?: (p?: object | null | undefined) => string | undefined;
|
||||||
toggleButton?: (p?: object | null | undefined) => string | undefined;
|
toggleButton?: (p?: object | null | undefined) => string | undefined;
|
||||||
footer?: (p?: object | null | undefined) => string | undefined;
|
footer?: (p?: object | null | undefined) => string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PopoverDirectionType = "up" | "down";
|
export type PopoverDirectionType = "up" | "down";
|
||||||
|
|
||||||
export interface DatepickerType {
|
export interface DatepickerType {
|
||||||
primaryColor?: ColorKeys;
|
primaryColor?: ColorKeys;
|
||||||
value: DateValueType;
|
value: DateValueType;
|
||||||
onChange: (value: DateValueType, e?: HTMLInputElement | null | undefined) => void;
|
onChange: (
|
||||||
useRange?: boolean;
|
value: DateValueType,
|
||||||
showFooter?: boolean;
|
e?: HTMLInputElement | null | undefined
|
||||||
showShortcuts?: boolean;
|
) => void;
|
||||||
configs?: Configs;
|
useRange?: boolean;
|
||||||
asSingle?: boolean;
|
showFooter?: boolean;
|
||||||
placeholder?: string;
|
showShortcuts?: boolean;
|
||||||
separator?: string;
|
configs?: Configs;
|
||||||
startFrom?: Date | null;
|
asSingle?: boolean;
|
||||||
i18n?: string;
|
placeholder?: string;
|
||||||
disabled?: boolean;
|
separator?: string;
|
||||||
classNames?: ClassNamesTypeProp | undefined;
|
startFrom?: Date | null;
|
||||||
containerClassName?: ((className: string) => string) | string | null;
|
i18n?: string;
|
||||||
inputClassName?: ((className: string) => string) | string | null;
|
disabled?: boolean;
|
||||||
toggleClassName?: ((className: string) => string) | string | null;
|
classNames?: ClassNamesTypeProp | undefined;
|
||||||
toggleIcon?: (open: boolean) => React.ReactNode;
|
containerClassName?: ((className: string) => string) | string | null;
|
||||||
inputId?: string;
|
inputClassName?: ((className: string) => string) | string | null;
|
||||||
inputName?: string;
|
toggleClassName?: ((className: string) => string) | string | null;
|
||||||
displayFormat?: string;
|
toggleIcon?: (open: boolean) => React.ReactNode;
|
||||||
readOnly?: boolean;
|
inputId?: string;
|
||||||
minDate?: Date | null;
|
inputName?: string;
|
||||||
maxDate?: Date | null;
|
displayFormat?: string;
|
||||||
dateLooking?: "forward" | "backward" | "middle";
|
readOnly?: boolean;
|
||||||
disabledDates?: DateRangeType[] | null;
|
minDate?: Date | null;
|
||||||
startWeekOn?: string | null;
|
maxDate?: Date | null;
|
||||||
popoverDirection?: PopoverDirectionType;
|
dateLooking?: "forward" | "backward" | "middle";
|
||||||
mode?: "daily" | "monthly";
|
disabledDates?: DateRangeType[] | null;
|
||||||
onMark?: (day: number, date: Date) => any;
|
startWeekOn?: string | null;
|
||||||
onLoad?: () => Promise<void>
|
popoverDirection?: PopoverDirectionType;
|
||||||
|
mode?: "daily" | "monthly";
|
||||||
|
onMark?: (day: number, date: Date, data?: any) => any;
|
||||||
|
onLoad?: (day: any) => Promise<void>;
|
||||||
|
style?: "custom" | "prasi";
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ColorKeys = (typeof COLORS)[number]; // "blue" | "orange"
|
export type ColorKeys = (typeof COLORS)[number]; // "blue" | "orange"
|
||||||
|
|
||||||
export interface Colors {
|
export interface Colors {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
[K in ColorKeys]: string;
|
[K in ColorKeys]: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import { FC, ReactElement, useEffect, useTransition } from "react";
|
||||||
|
import { Popover } from "../Popover/Popover";
|
||||||
|
import { TypeColor } from "../form/field/TypeColor";
|
||||||
|
|
||||||
|
export const FieldColorPicker: FC<{
|
||||||
|
children: ReactElement;
|
||||||
|
value?: string;
|
||||||
|
update: (value: string) => void;
|
||||||
|
open: boolean;
|
||||||
|
onOpen?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
showHistory?: boolean;
|
||||||
|
}> = ({ children, value, update, open, onClose, onOpen, showHistory }) => {
|
||||||
|
const local = useLocal({ show: open || false });
|
||||||
|
useEffect(() => {
|
||||||
|
if (value) {
|
||||||
|
local.show = open || false;
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
}, [value, open]);
|
||||||
|
const [_, tx] = useTransition();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={local.show}
|
||||||
|
onOpenChange={(open: any) => {
|
||||||
|
local.show = open;
|
||||||
|
if (open && onOpen) {
|
||||||
|
onOpen();
|
||||||
|
} else if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
local.render();
|
||||||
|
}}
|
||||||
|
backdrop={false}
|
||||||
|
popoverClassName="rounded-md p-2 text-sm bg-white shadow-2xl border border-slate-300"
|
||||||
|
content={
|
||||||
|
<TypeColor
|
||||||
|
value={value}
|
||||||
|
showHistory={showHistory}
|
||||||
|
onClose={() => {
|
||||||
|
local.show = false;
|
||||||
|
local.render();
|
||||||
|
if (onClose) onClose();
|
||||||
|
}}
|
||||||
|
onChangePicker={(color: any) => {
|
||||||
|
tx(() => {
|
||||||
|
if (color.indexOf("NaN") < 0) {
|
||||||
|
update(color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
local.show = true;
|
||||||
|
local.render();
|
||||||
|
if (onOpen) onOpen();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -17,8 +17,6 @@ export const PinterestLayout: React.FC<{
|
||||||
});
|
});
|
||||||
return columns;
|
return columns;
|
||||||
};
|
};
|
||||||
const layout_grid = new Array(col);
|
|
||||||
const columns = createColumns(data, col); // Membagi data ke dalam kolom
|
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
data: [] as any[],
|
data: [] as any[],
|
||||||
ids: {
|
ids: {
|
||||||
|
|
@ -33,8 +31,6 @@ export const PinterestLayout: React.FC<{
|
||||||
const targetColumn = index % col; // Menentukan kolom target berdasarkan indeks
|
const targetColumn = index % col; // Menentukan kolom target berdasarkan indeks
|
||||||
columns[targetColumn].push(item); // Memasukkan elemen ke kolom yang sesuai
|
columns[targetColumn].push(item); // Memasukkan elemen ke kolom yang sesuai
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Columns:", columns); // Debugging
|
|
||||||
local.data = columns;
|
local.data = columns;
|
||||||
local.render();
|
local.render();
|
||||||
}, [data, col]);
|
}, [data, col]);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||||
|
import { ChevronDown } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils/utils";
|
||||||
|
|
||||||
|
const Accordion = AccordionPrimitive.Root;
|
||||||
|
|
||||||
|
const AccordionItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn("border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AccordionItem.displayName = "AccordionItem";
|
||||||
|
|
||||||
|
const AccordionTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Header className="flex">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
));
|
||||||
|
const AccordionTriggerCustom: React.FC<any> = (
|
||||||
|
{ className, onRightLabel, children, ...props },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
|
<AccordionPrimitive.Header className="flex flex-row items-center">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
{typeof onRightLabel === "function" && onRightLabel()}
|
||||||
|
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
);
|
||||||
|
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
|
const AccordionContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className="data-[state=closed]:overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
));
|
||||||
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Accordion,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionTriggerCustom,
|
||||||
|
};
|
||||||
|
|
@ -18,7 +18,7 @@ export const Alert: FC<any> = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
content,
|
content,
|
||||||
msg
|
msg,
|
||||||
}) => {
|
}) => {
|
||||||
const message: any = {
|
const message: any = {
|
||||||
save: "Your data will be saved securely. You can update it at any time if needed.",
|
save: "Your data will be saved securely. You can update it at any time if needed.",
|
||||||
|
|
@ -35,10 +35,9 @@ export const Alert: FC<any> = ({
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
|
|
||||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{message?.[type] || msg}
|
{msg || message?.[type]}
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
export const ButtonRichText: FC<any> = ({
|
||||||
|
children,
|
||||||
|
active,
|
||||||
|
onClick,
|
||||||
|
disabled,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
className={cx(
|
||||||
|
active ? "is-active bg-gray-200" : "",
|
||||||
|
"text-black text-sm p-1 hover:bg-gray-200 rounded-md px-2 "
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -9,16 +9,15 @@ const btn = cva(
|
||||||
" text-white px-4 py-1.5 group active-menu-icon relative flex items-stretch justify-center p-0.5 text-center border border-transparent text-white enabled:hover:bg-cyan-800 rounded-md"
|
" text-white px-4 py-1.5 group active-menu-icon relative flex items-stretch justify-center p-0.5 text-center border border-transparent text-white enabled:hover:bg-cyan-800 rounded-md"
|
||||||
);
|
);
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"cursor-pointer px-4 py-1.5 group relative flex items-stretch justify-center p-0.5 text-center border inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
"cursor-pointer px-4 py-1.5 group relative flex items-stretch justify-center p-0.5 text-center inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"bg-primary text-white shadow hover:bg-primary/90 active-menu-icon",
|
"border bg-primary text-white shadow hover:bg-primary/90 active-menu-icon",
|
||||||
reject:
|
reject:
|
||||||
"bg-red-500 text-white shadow hover:bg-red-500 active-menu-icon",
|
"bg-red-500 text-white shadow hover:bg-red-500 active-menu-icon",
|
||||||
destructive:
|
destructive: "bg-red-500 text-white shadow-sm hover:bg-destructive/90",
|
||||||
"bg-red-500 text-white shadow-sm hover:bg-destructive/90",
|
|
||||||
outline:
|
outline:
|
||||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary:
|
secondary:
|
||||||
|
|
@ -58,8 +57,12 @@ const ButtonBetter = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const ButtonContainer: FC<any> = ({ children, className, variant = "default" }) => {
|
const ButtonContainer: FC<any> = ({
|
||||||
const vr = variant ? variant: "default"
|
children,
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
}) => {
|
||||||
|
const vr = variant ? variant : "default";
|
||||||
return (
|
return (
|
||||||
<div className={cx(buttonVariants({ variant: vr, className }))}>
|
<div className={cx(buttonVariants({ variant: vr, className }))}>
|
||||||
<div className="flex items-center gap-x-0.5 text-sm">{children}</div>
|
<div className="flex items-center gap-x-0.5 text-sm">{children}</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import api from "../utils/axios";
|
||||||
|
import { userRoleMe } from "../utils/getAccess";
|
||||||
|
|
||||||
|
export const userToken = async () => {
|
||||||
|
const user = localStorage.getItem("user");
|
||||||
|
if (user) {
|
||||||
|
const w = window as any;
|
||||||
|
w.user = JSON.parse(user);
|
||||||
|
}else{
|
||||||
|
const res = await api.get(`${process.env.NEXT_PUBLIC_API_PORTAL}/api/check-jwt-token`);
|
||||||
|
const jwt = res.data.data;
|
||||||
|
console.log({jwt})
|
||||||
|
if (!jwt) return ;
|
||||||
|
try {
|
||||||
|
await api.post(process.env.NEXT_PUBLIC_BASE_URL + "/api/cookies", {
|
||||||
|
token: jwt,
|
||||||
|
});
|
||||||
|
const user = await api.get(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_PORTAL}/api/users/me`
|
||||||
|
);
|
||||||
|
const us = user.data.data;
|
||||||
|
console.log({us})
|
||||||
|
if (us) {
|
||||||
|
localStorage.setItem("user", JSON.stringify(user.data.data));
|
||||||
|
const roles = await userRoleMe();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
console.log({res})
|
||||||
|
}
|
||||||
|
};
|
||||||
16
package.json
16
package.json
|
|
@ -6,6 +6,7 @@
|
||||||
"@emotion/css": "^11.13.5",
|
"@emotion/css": "^11.13.5",
|
||||||
"@faker-js/faker": "^9.2.0",
|
"@faker-js/faker": "^9.2.0",
|
||||||
"@floating-ui/react": "^0.26.28",
|
"@floating-ui/react": "^0.26.28",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@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",
|
||||||
|
|
@ -13,6 +14,18 @@
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
"@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-link": "^2.11.2",
|
||||||
|
"@tiptap/extension-list-item": "^2.11.2",
|
||||||
|
"@tiptap/extension-table": "^2.11.2",
|
||||||
|
"@tiptap/extension-table-cell": "^2.11.2",
|
||||||
|
"@tiptap/extension-table-header": "^2.11.2",
|
||||||
|
"@tiptap/extension-table-row": "^2.11.2",
|
||||||
|
"@tiptap/extension-text-align": "^2.11.2",
|
||||||
|
"@tiptap/extension-underline": "^2.11.2",
|
||||||
|
"@tiptap/pm": "^2.11.2",
|
||||||
|
"@tiptap/react": "^2.11.2",
|
||||||
|
"@tiptap/starter-kit": "^2.11.2",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/lodash.get": "^4.4.9",
|
"@types/lodash.get": "^4.4.9",
|
||||||
"@types/lodash.uniqby": "^4.7.9",
|
"@types/lodash.uniqby": "^4.7.9",
|
||||||
|
|
@ -35,10 +48,12 @@
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"next": "15.0.3",
|
"next": "15.0.3",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^2.1.7",
|
||||||
"react-slick": "^0.30.2",
|
"react-slick": "^0.30.2",
|
||||||
"sonner": "^1.7.0",
|
"sonner": "^1.7.0",
|
||||||
|
"tinycolor2": "^1.6.0",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
|
|
@ -47,6 +62,7 @@
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"@types/react-slick": "^0.23.13",
|
"@types/react-slick": "^0.23.13",
|
||||||
|
"@types/tinycolor2": "^1.4.6",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "15.0.3",
|
"eslint-config-next": "15.0.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import get from "lodash.get";
|
||||||
|
import api from "./axios";
|
||||||
|
|
||||||
|
type apixType = {
|
||||||
|
port: "portal" | "recruitment" | "mpp";
|
||||||
|
path: string;
|
||||||
|
method?: "get" | "delete" | "post" | "put";
|
||||||
|
data?: any;
|
||||||
|
value?: any;
|
||||||
|
validate?: "object" | "array" | "dropdown";
|
||||||
|
keys?: {
|
||||||
|
value?: string;
|
||||||
|
label: string | ((item: any) => string);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const apix = async ({
|
||||||
|
port = "portal",
|
||||||
|
method = "get",
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
path,
|
||||||
|
validate = "object",
|
||||||
|
keys,
|
||||||
|
}: apixType) => {
|
||||||
|
const root_url = `${
|
||||||
|
port === "portal"
|
||||||
|
? process.env.NEXT_PUBLIC_API_PORTAL
|
||||||
|
: port === "recruitment"
|
||||||
|
? process.env.NEXT_PUBLIC_API_RECRUITMENT
|
||||||
|
: port === "mpp"
|
||||||
|
? process.env.NEXT_PUBLIC_API_MPP
|
||||||
|
: ""
|
||||||
|
}${path}`;
|
||||||
|
let result = null as any;
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
switch (method) {
|
||||||
|
case "get":
|
||||||
|
result = await api.get(root_url);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "post":
|
||||||
|
result = await api.post(root_url, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "put":
|
||||||
|
result = await api.put(root_url, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "delete":
|
||||||
|
result = await api.delete(root_url, data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported HTTP method: ${method}`);
|
||||||
|
}
|
||||||
|
} catch (ex: any) {
|
||||||
|
console.error(
|
||||||
|
"API Error:",
|
||||||
|
get(ex, "response.data.meta.message") || ex.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const val = get(result, value);
|
||||||
|
return validate === "object"
|
||||||
|
? get(result, value)
|
||||||
|
: validate === "dropdown" && Array.isArray(get(result, value))
|
||||||
|
? val.map((e: any) => {
|
||||||
|
return {
|
||||||
|
value: keys?.value ? get(e, keys?.value) : get(e, "id"),
|
||||||
|
label:
|
||||||
|
typeof keys?.label === "function"
|
||||||
|
? keys.label(e)
|
||||||
|
: keys?.label
|
||||||
|
? get(e, keys?.label)
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: Array.isArray(get(result, value))
|
||||||
|
? get(result, value)
|
||||||
|
: [];
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("API Error:", error.response || error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export const cloneFM = (fm: any, row: any) => {
|
export const cloneFM = (fm: any, row: any) => {
|
||||||
// const result -
|
return {
|
||||||
return {
|
...fm,
|
||||||
...fm,
|
data: row,
|
||||||
data: row
|
render: fm.render,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
export const labelDocumentType = (value?: string) => {
|
||||||
|
switch (value) {
|
||||||
|
case "ADMINISTRATIVE_SELECTION":
|
||||||
|
return "Administrative";
|
||||||
|
break;
|
||||||
|
case "TEST":
|
||||||
|
return "Test";
|
||||||
|
break;
|
||||||
|
case "INTERVIEW":
|
||||||
|
return "Interview";
|
||||||
|
break;
|
||||||
|
case "FGD":
|
||||||
|
return "FGD";
|
||||||
|
break;
|
||||||
|
case "SURAT_PENGANTAR_MASUK":
|
||||||
|
return "Surat Pengantar Masuk";
|
||||||
|
break;
|
||||||
|
case "SURAT_IZIN_ORANG_TUA":
|
||||||
|
return "Surat Izin Orang Tua";
|
||||||
|
break;
|
||||||
|
case "FINAL_INTERVIEW":
|
||||||
|
return "Final Interview";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "KARYAWAN_TETAP":
|
||||||
|
return "Karyawan Tetap";
|
||||||
|
case "OFFERING_LETTER":
|
||||||
|
return "Offering Letter";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "CONTRACT_DOCUMENT":
|
||||||
|
return "Contract Document";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "DOCUMENT_CHECKING":
|
||||||
|
return "Document Checking";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,23 +3,32 @@ import { generateQueryString } from "./generateQueryString";
|
||||||
|
|
||||||
type EventActions = "before-onload" | "onload-param" | string;
|
type EventActions = "before-onload" | "onload-param" | string;
|
||||||
export const events = async (action: EventActions, data: any) => {
|
export const events = async (action: EventActions, data: any) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "onload-param":
|
case "onload-param":
|
||||||
|
let params = {
|
||||||
|
...data,
|
||||||
|
page: get(data, "paging"),
|
||||||
|
page_size: get(data, "take"),
|
||||||
|
search: get(data, "search"),
|
||||||
|
};
|
||||||
|
params = {
|
||||||
|
...params,
|
||||||
|
};
|
||||||
|
if (params?.sort) {
|
||||||
|
params = {
|
||||||
|
...params,
|
||||||
|
...params?.sort,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
delete params["sort"];
|
||||||
|
delete params["paging"];
|
||||||
|
delete params["take"];
|
||||||
|
return generateQueryString(params);
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
const params = {
|
default:
|
||||||
...data,
|
break;
|
||||||
page: get(data, "paging"),
|
}
|
||||||
page_size: get(data, "take"),
|
return null;
|
||||||
search: get(data, "search")
|
};
|
||||||
};
|
|
||||||
delete params["paging"]
|
|
||||||
delete params["take"]
|
|
||||||
return generateQueryString(params)
|
|
||||||
return
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue