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 { TypeTag } from "./field/TypeTag";
import get from "lodash.get"; import get from "lodash.get";
import { getNumber } from "@/lib/utils/getNumber"; import { getNumber } from "@/lib/utils/getNumber";
import { useLocal } from "@/lib/utils/use-local";
export const Field: React.FC<any> = ({ export const Field: React.FC<any> = ({
fm, fm,
@ -26,7 +27,9 @@ export const Field: React.FC<any> = ({
suffix, suffix,
}) => { }) => {
let result = null; let result = null;
const field = useLocal({
focus: false,
});
const suffixRef = useRef<HTMLDivElement | null>(null); const suffixRef = useRef<HTMLDivElement | null>(null);
const prefixRef = 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;
@ -58,7 +61,25 @@ export const Field: React.FC<any> = ({
<div <div
className={cx( className={cx(
"flex", "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 ? ( {!hidden_label ? (
@ -78,22 +99,34 @@ export const Field: React.FC<any> = ({
error error
? "flex flex-row rounded-md flex-grow border-red-500 border items-center" ? "flex flex-row rounded-md flex-grow border-red-500 border items-center"
: "flex flex-row rounded-md flex-grow items-center", : "flex flex-row rounded-md flex-grow items-center",
is_disable ? "bg-gray-100" : "", is_disable
"relative", ? "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 && ( {before && (
<div <div
ref={prefixRef} // ref={prefixRef}
className={cx( 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` css`
height: 2.13rem; height: 2.13rem;
top: 50%;
transform: translateY(-50%);
`, `,
is_disable ? "bg-gray-100" : "bg-gray-200/50" is_disable ? "" : ""
)} )}
> >
{before} {before}
@ -151,7 +184,7 @@ export const Field: React.FC<any> = ({
onLoad={onLoad} onLoad={onLoad}
placeholder={placeholder} placeholder={placeholder}
disabled={is_disable} disabled={is_disable}
on_change={onChange} onChange={onChange}
className={className} className={className}
/> />
</> </>
@ -163,8 +196,8 @@ export const Field: React.FC<any> = ({
onLoad={onLoad} onLoad={onLoad}
placeholder={placeholder} placeholder={placeholder}
disabled={is_disable} disabled={is_disable}
on_change={onChange}
className={className} className={className}
onChange={onChange}
mode="single" mode="single"
/> />
</> </>
@ -175,6 +208,7 @@ export const Field: React.FC<any> = ({
name={name} name={name}
disabled={is_disable} disabled={is_disable}
className={className} className={className}
onChange={onChange}
/> />
</> </>
) : ["tag"].includes(type) ? ( ) : ["tag"].includes(type) ? (
@ -184,6 +218,7 @@ export const Field: React.FC<any> = ({
name={name} name={name}
disabled={is_disable} disabled={is_disable}
className={className} className={className}
onChange={onChange}
/> />
</> </>
) : ( ) : (
@ -196,6 +231,10 @@ export const Field: React.FC<any> = ({
type={type} type={type}
disabled={is_disable} disabled={is_disable}
onChange={onChange} onChange={onChange}
onFocus={() => {
field.focus = true;
field.render();
}}
className={cx( className={cx(
before && before &&
css` css`
@ -215,7 +254,7 @@ export const Field: React.FC<any> = ({
)} )}
{after && ( {after && (
<div <div
ref={suffixRef} // ref={suffixRef}
className={cx( className={cx(
"absolute right-[1px] px-1 py-1 items-center flex flex-row flex-grow rounded-r-md", "absolute right-[1px] px-1 py-1 items-center flex flex-row flex-grow rounded-r-md",
css` 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.data[name] = val;
} }
fm.render(); fm.render();
if (typeof onChange === "function") {
onChange(fm.data[name]);
}
}; };
return ( return (
<> <>

View File

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

View File

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

View File

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

View File

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

View File

@ -147,7 +147,7 @@ export const FieldUploadSingle: FC<{
<> <>
<div <div
className={cx( 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` css`
input[type="file"], input[type="file"],
input[type="file"]::-webkit-file-upload-button { input[type="file"]::-webkit-file-upload-button {

View File

@ -385,7 +385,7 @@ export const Typeahead: FC<{
<div <div
className={cx( className={cx(
local.mode === "single" ? "cursor-pointer" : "cursor-text", 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 className
)} )}
onClick={() => { onClick={() => {

View File

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

View File

@ -807,6 +807,100 @@ export const Pagination: React.FC<any> = ({
</div> </div>
); );
}; };
export const PaginationPage: React.FC<any> = ({
onNextPage,
onPrevPage,
disabledNextPage,
disabledPrevPage,
page,
count,
list,
take,
setPage,
onChangePage,
}) => {
const local = useLocal({
page: 1 as any,
pagination: [] as any,
});
useEffect(() => {
local.page = page;
local.pagination = getPagination(page, Math.ceil(count / take));
local.render();
}, [page, count]);
return (
<div className=" 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 getPagination = (currentPage: number, totalPages: number) => {
const pagination: { label: string; active: boolean }[] = []; 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" " 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 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: { variants: {
variant: { variant: {

View File

@ -1,6 +1,6 @@
import dotenv from "dotenv"; import dotenv from "dotenv";
dotenv.config(); dotenv.config();
export const siteurl = (param: string) => { 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}`; return `${process.env.NEXT_PUBLIC_BASE_URL + param}`;
}; };