This commit is contained in:
faisolavolut 2025-01-22 09:50:32 +07:00
parent bec966b3fa
commit 13198aea73
14 changed files with 228 additions and 40 deletions

View File

@ -8,6 +8,7 @@ import { TypeRichText } from "./field/TypeRichText";
import { TypeTag } from "./field/TypeTag";
import get from "lodash.get";
import { getNumber } from "@/lib/utils/getNumber";
import { useLocal } from "@/lib/utils/use-local";
export const Field: React.FC<any> = ({
fm,
@ -26,7 +27,9 @@ export const Field: React.FC<any> = ({
suffix,
}) => {
let result = null;
const field = useLocal({
focus: false,
});
const suffixRef = useRef<HTMLDivElement | null>(null);
const prefixRef = useRef<HTMLDivElement | null>(null);
const is_disable = fm.mode === "view" ? true : disabled;
@ -58,7 +61,25 @@ export const Field: React.FC<any> = ({
<div
className={cx(
"flex",
style === "inline" ? "flex-row gap-x-1" : "flex-col"
style === "inline" ? "flex-row gap-x-1" : "flex-col",
css`
.field input:focus {
outline: 0px !important;
border: 0px !important;
outline-offset: 0px !important;
--tw-ring-color: transparent !important;
}
.field textarea:focus {
outline: 0px !important;
border: 0px !important;
outline-offset: 0px !important;
--tw-ring-color: transparent !important;
}
.field input {
border: 0px !important;
box-shadow: none;
}
`
)}
>
{!hidden_label ? (
@ -78,22 +99,34 @@ export const Field: React.FC<any> = ({
error
? "flex flex-row rounded-md flex-grow border-red-500 border items-center"
: "flex flex-row rounded-md flex-grow items-center",
is_disable ? "bg-gray-100" : "",
"relative",
""
is_disable
? "border border-gray-100 bg-gray-100"
: "border border-gray-300 ",
"relative field",
!is_disable
? style === "underline"
? "focus-within:border-b focus-within:border-b-gray-500"
: "focus-within:border focus-within:border-gray-500"
: "",
style === "underline"
? "rounded-none border-transparent border-b-gray-300"
: "",
["rating", "color", "single-checkbox", "checkbox"].includes(type) &&
css`
border: 0px !important;
`
)}
>
{before && (
<div
ref={prefixRef}
// 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",
"px-1 py-1 items-center flex flex-row flex-grow rounded-l-md h-full",
css`
height: 2.13rem;
top: 50%;
transform: translateY(-50%);
`,
is_disable ? "bg-gray-100" : "bg-gray-200/50"
is_disable ? "" : ""
)}
>
{before}
@ -151,7 +184,7 @@ export const Field: React.FC<any> = ({
onLoad={onLoad}
placeholder={placeholder}
disabled={is_disable}
on_change={onChange}
onChange={onChange}
className={className}
/>
</>
@ -163,8 +196,8 @@ export const Field: React.FC<any> = ({
onLoad={onLoad}
placeholder={placeholder}
disabled={is_disable}
on_change={onChange}
className={className}
onChange={onChange}
mode="single"
/>
</>
@ -175,6 +208,7 @@ export const Field: React.FC<any> = ({
name={name}
disabled={is_disable}
className={className}
onChange={onChange}
/>
</>
) : ["tag"].includes(type) ? (
@ -184,6 +218,7 @@ export const Field: React.FC<any> = ({
name={name}
disabled={is_disable}
className={className}
onChange={onChange}
/>
</>
) : (
@ -196,6 +231,10 @@ export const Field: React.FC<any> = ({
type={type}
disabled={is_disable}
onChange={onChange}
onFocus={() => {
field.focus = true;
field.render();
}}
className={cx(
before &&
css`
@ -215,7 +254,7 @@ export const Field: React.FC<any> = ({
)}
{after && (
<div
ref={suffixRef}
// ref={suffixRef}
className={cx(
"absolute right-[1px] px-1 py-1 items-center flex flex-row flex-grow rounded-r-md",
css`

View File

@ -0,0 +1,9 @@
import { FC, useEffect } from "react";
export const InitEditor: FC<any> = ({ local, editor }) => {
useEffect(() => {
local.editor = editor;
local.render();
}, []);
return <div></div>;
};

View File

@ -63,6 +63,10 @@ export const FieldCheckbox: FC<any> = ({
fm.data[name] = val;
}
fm.render();
if (typeof onChange === "function") {
onChange(fm.data[name]);
}
};
return (
<>

View File

@ -10,6 +10,7 @@ export const TypeColor: React.FC<any> = ({
value,
onChangePicker,
onClose,
onChange,
}) => {
const meta = useLocal({
originalValue: "",

View File

@ -70,12 +70,7 @@ export const TypeInput: React.FC<any> = ({
disabled={disabled}
required={required}
className={cx(
"text-sm",
error
? css`
border-color: red !important;
`
: ``,
"text-sm border-none",
css`
background-color: ${disabled
? "rgb(243 244 246)"
@ -112,6 +107,9 @@ export const TypeInput: React.FC<any> = ({
onRatingChange={(e) => {
fm.data[name] = getNumber(e);
fm.render();
if (typeof onChange === "function") {
onChange(fm.data[name]);
}
}}
/>
</div>
@ -160,6 +158,9 @@ export const TypeInput: React.FC<any> = ({
update={(val) => {
fm.data[name] = val;
fm.render();
if (typeof onChange === "function") {
onChange(fm.data[name]);
}
}}
onOpen={() => {
input.open = true;

View File

@ -1,12 +1,7 @@
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 { useEffect, useRef, useState } from "react";
import { useCurrentEditor, EditorProvider } from "@tiptap/react";
import Underline from "@tiptap/extension-underline";
import Link from "@tiptap/extension-link";
import StarterKit from "@tiptap/starter-kit";
@ -23,6 +18,7 @@ import TableCell from "@tiptap/extension-table-cell";
import TableHeader from "@tiptap/extension-table-header";
import TableRow from "@tiptap/extension-table-row";
import { ButtonRichText } from "../../ui/button-rich-text";
import { InitEditor } from "./AfterEditor";
export const TypeRichText: React.FC<any> = ({
name,
@ -35,10 +31,14 @@ export const TypeRichText: React.FC<any> = ({
onChange,
}) => {
let value: any = fm.data?.[name] || "";
const editorRef = useRef(null);
const [content, setContent] = useState(``);
const input = useLocal({
value: 0 as any,
value: `` as any,
ref: null as any,
open: false,
editor: null as any,
});
const [url, setUrl] = useState(null as any);
const local = useLocal({
@ -47,9 +47,13 @@ export const TypeRichText: React.FC<any> = ({
tab: 0,
active: "General",
});
useEffect(() => {}, [fm.data?.[name]]);
useEffect(() => {}, []);
useEffect(() => {
try {
fm.fields[name] = { ...fm.fields?.[name], ...input };
fm.render();
} catch (e) {}
}, []);
const MenuBar = () => {
const { editor } = useCurrentEditor();
if (disabled) return <></>;
@ -766,11 +770,18 @@ export const TypeRichText: React.FC<any> = ({
},
}),
];
const AfterEditor = () => {
const { editor } = useCurrentEditor();
if (!editor) return <></>;
return <InitEditor editor={editor} local={local} />;
};
return (
<div
ref={(e) => {
if (e) input.ref = e;
}}
className={cx(
"flex flex-col relative bg-white border border-gray-300 rounded-md w-full richtext-field",
"flex flex-col relative bg-white rounded-md w-full richtext-field",
css`
.tiptap h1 {
font-size: 1.4rem !important;
@ -812,9 +823,13 @@ export const TypeRichText: React.FC<any> = ({
onUpdate={({ editor }) => {
fm.data[name] = editor.getHTML();
fm.render();
if (typeof onChange === "function") {
onChange(fm.data[name]);
}
}}
content={fm.data[name]}
content={input.value}
editable={!disabled}
slotAfter={<AfterEditor />}
></EditorProvider>
</div>
);

View File

@ -48,6 +48,9 @@ export const TypeTag: React.FC<any> = ({
setTags(updatedTags);
setEditingIndex(null); // Keluar dari mode edit
setTempValue(""); // Reset nilai sementara
if (typeof onChange === "function") {
onChange(tags);
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!disabled) return;
@ -55,8 +58,14 @@ export const TypeTag: React.FC<any> = ({
e.preventDefault();
setTags([...tags, inputValue]);
setInputValue("");
if (typeof onChange === "function") {
onChange(tags);
}
} else if (e.key === "Backspace" && !inputValue && tags.length > 0) {
setTags(tags.slice(0, -1));
if (typeof onChange === "function") {
onChange(tags);
}
}
};
const handleFocusTag = (index: number) => {
@ -70,10 +79,13 @@ export const TypeTag: React.FC<any> = ({
const removeTag = (index: number) => {
if (!disabled) return;
setTags(tags.filter((_, i) => i !== index));
if (typeof onChange === "function") {
onChange(tags);
}
};
return (
<div className="flex flex-wrap items-center border border-gray-300 rounded-md flex-grow ">
<div className="flex flex-wrap items-center rounded-md flex-grow ">
{tags.map((tag, index) => (
<div
key={index}

View File

@ -147,7 +147,7 @@ export const FieldUploadSingle: FC<{
<>
<div
className={cx(
"hover:bg-gray-50 border border-gray-300 text-gray-900 text-md rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-1.5 ",
"hover:bg-gray-50 text-gray-900 text-md rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-1.5 ",
css`
input[type="file"],
input[type="file"]::-webkit-file-upload-button {

View File

@ -385,7 +385,7 @@ export const Typeahead: FC<{
<div
className={cx(
local.mode === "single" ? "cursor-pointer" : "cursor-text",
"text-black flex relative flex-wrap py-0 items-center w-full h-full flex-1 rounded-md border border-gray-300 overflow-hidden ",
"text-black flex relative flex-wrap py-0 items-center w-full h-full flex-1 ",
className
)}
onClick={() => {

View File

@ -69,7 +69,7 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
}, [isParentActive]);
const itemStyle = {
paddingLeft:
!hasChildren && !depth ? "16px" : !mini ? `${depth * 16}px` : "0px",
!hasChildren && !depth ? "13px" : !mini ? `${depth * 16}px` : "0px",
};
return (
<React.Fragment key={item.href || item.title || index}>
@ -248,7 +248,7 @@ const SidebarTree: React.FC<TreeMenuProps> = ({ data, minimaze, mini }) => {
? " bg-layer font-normal vertical-rounded-tab"
: " bg-layer text-primary font-bold vertical-rounded-tab"
: "text-white",
!depth && !hasChildren ? "px-3" : "",
!depth && !hasChildren ? "px-2" : "",
css`
& > span {
white-space: wrap !important;

View File

@ -807,6 +807,100 @@ export const Pagination: React.FC<any> = ({
</div>
);
};
export const PaginationPage: React.FC<any> = ({
onNextPage,
onPrevPage,
disabledNextPage,
disabledPrevPage,
page,
count,
list,
take,
setPage,
onChangePage,
}) => {
const local = useLocal({
page: 1 as any,
pagination: [] as any,
});
useEffect(() => {
local.page = page;
local.pagination = getPagination(page, Math.ceil(count / take));
local.render();
}, [page, count]);
return (
<div className=" tbl-pagination text-sm bottom-0 right-0 w-full grid grid-cols-1 gap-4 justify-center text-sm bg-white pt-2">
<div className="flex flex-row items-center justify-center">
<div className="flex items-center flex-row gap-x-2 sm:mb-0 text-sm">
<div
onClick={() => {
if (!disabledPrevPage) {
onPrevPage();
}
}}
className={cx(
"flex flex-row items-center gap-x-2 justify-center rounded-full p-2 text-md",
disabledPrevPage
? "text-gray-200 border-gray-200 border "
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border "
)}
>
<HiChevronLeft />
</div>
<div className="flex flex-row justify-center">
<div>
<nav
className="isolate inline-flex -space-x-px flex flex-row items-center gap-x-2"
aria-label="Pagination"
>
{local.pagination.map((e: any, idx: number) => {
return (
<div
key={"page_" + idx}
onClick={() => {
if (e?.label !== "...") {
local.page = getNumber(e?.label);
local.render();
onChangePage(local.page - 1);
setPage(local.page - 1);
}
}}
className={cx(
"text-md px-2.5 py-1",
e.active
? "relative z-10 inline-flex items-center bg-primary font-semibold text-white rounded-full"
: e?.label === "..."
? "relative z-10 inline-flex items-center font-semibold text-gray-800 rounded-full"
: "cursor-pointer relative z-10 inline-flex items-center hover:bg-gray-100 font-semibold text-gray-800 rounded-full"
)}
>
{e?.label}
</div>
);
})}
</nav>
</div>
</div>
<div
onClick={() => {
if (!disabledNextPage) {
onNextPage();
}
}}
className={cx(
"flex flex-row items-center gap-x-2 justify-center rounded-full p-2 ",
disabledNextPage
? "text-gray-200 border-gray-200 border"
: "cursor-pointer text-gray-500 hover:bg-gray-100 hover:text-gray-900 border-gray-500 border "
)}
>
<HiChevronRight className="text-md" />
</div>
</div>
</div>
</div>
);
};
const getPagination = (currentPage: number, totalPages: number) => {
const pagination: { label: string; active: boolean }[] = [];

View File

@ -0,0 +1,13 @@
import React, { createContext, FC, useContext } from "react";
const EditorContext = createContext(null);
export const EditorProvider: FC<any> = ({ editor, children }) => {
return (
<EditorContext.Provider value={editor}>{children}</EditorContext.Provider>
);
};
export const useCurrentEditor = () => {
return useContext(EditorContext);
};

View File

@ -9,7 +9,7 @@ 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"
);
const buttonVariants = cva(
"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",
"cursor-pointer px-4 py-1.5 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: {
variant: {

View File

@ -1,6 +1,6 @@
import dotenv from "dotenv";
dotenv.config();
export const siteurl = (param: string) => {
if (param.startsWith("http")) return param;
if (param && param.startsWith("http")) return param;
return `${process.env.NEXT_PUBLIC_BASE_URL + param}`;
};