dropdown search allow new value
This commit is contained in:
parent
d19dbe77eb
commit
be558f775b
|
|
@ -22,8 +22,11 @@ export const TypeDropdown: React.FC<any> = ({
|
||||||
? [fm.data?.[name]]
|
? [fm.data?.[name]]
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
|
allowNew={true}
|
||||||
|
unique={false}
|
||||||
disabledSearch={false}
|
disabledSearch={false}
|
||||||
// popupClassName={}
|
// popupClassName={}
|
||||||
|
fitur="search-add"
|
||||||
required={required}
|
required={required}
|
||||||
onSelect={({ search, item }) => {
|
onSelect={({ search, item }) => {
|
||||||
if (item) {
|
if (item) {
|
||||||
|
|
@ -42,10 +45,11 @@ export const TypeDropdown: React.FC<any> = ({
|
||||||
if (typeof onChange === "function" && item) {
|
if (typeof onChange === "function" && item) {
|
||||||
onChange(item);
|
onChange(item);
|
||||||
}
|
}
|
||||||
|
console.log(fm.data[name]);
|
||||||
return item?.value || search;
|
return item?.value || search;
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
allowNew={false}
|
// allowNew={false}
|
||||||
autoPopupWidth={true}
|
autoPopupWidth={true}
|
||||||
focusOpen={true}
|
focusOpen={true}
|
||||||
mode={mode ? mode : "single"}
|
mode={mode ? mode : "single"}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
import { FC, KeyboardEvent, useCallback, useEffect, useRef } from "react";
|
import {
|
||||||
|
FC,
|
||||||
|
KeyboardEvent,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
import { TypeaheadOptions } from "./typeahead-opt";
|
import { TypeaheadOptions } from "./typeahead-opt";
|
||||||
|
|
@ -11,6 +18,7 @@ import uniqBy from "lodash.uniqby";
|
||||||
type OptItem = { value: string; label: string; tag?: string };
|
type OptItem = { value: string; label: string; tag?: string };
|
||||||
|
|
||||||
export const Typeahead: FC<{
|
export const Typeahead: FC<{
|
||||||
|
fitur?: "search-add";
|
||||||
value?: string[] | null;
|
value?: string[] | null;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
|
@ -34,6 +42,7 @@ export const Typeahead: FC<{
|
||||||
onInit?: (e: any) => void;
|
onInit?: (e: any) => void;
|
||||||
}> = ({
|
}> = ({
|
||||||
value,
|
value,
|
||||||
|
fitur,
|
||||||
note,
|
note,
|
||||||
options: options_fn,
|
options: options_fn,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
|
@ -51,6 +60,8 @@ export const Typeahead: FC<{
|
||||||
disabledSearch,
|
disabledSearch,
|
||||||
onInit,
|
onInit,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [debouncedTerm, setDebouncedTerm] = useState("");
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
value: [] as string[],
|
value: [] as string[],
|
||||||
open: false,
|
open: false,
|
||||||
|
|
@ -133,7 +144,7 @@ export const Typeahead: FC<{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(local.unique);
|
||||||
if (local.unique) {
|
if (local.unique) {
|
||||||
let found = local.value.find((e) => {
|
let found = local.value.find((e) => {
|
||||||
return e === arg.item?.value || arg.search === e;
|
return e === arg.item?.value || arg.search === e;
|
||||||
|
|
@ -194,7 +205,6 @@ export const Typeahead: FC<{
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const selected = select({
|
const selected = select({
|
||||||
search: local.search.input,
|
search: local.search.input,
|
||||||
item: local.select,
|
item: local.select,
|
||||||
|
|
@ -217,6 +227,7 @@ export const Typeahead: FC<{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (options.length > 0) {
|
if (options.length > 0) {
|
||||||
|
console.log("HALOOO");
|
||||||
local.open = true;
|
local.open = true;
|
||||||
if (e.key === "ArrowDown") {
|
if (e.key === "ArrowDown") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -268,9 +279,11 @@ export const Typeahead: FC<{
|
||||||
search: local.search.input,
|
search: local.search.input,
|
||||||
existing: options,
|
existing: options,
|
||||||
});
|
});
|
||||||
|
console.log({ res });
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
const applyOptions = (result: (string | OptItem)[]) => {
|
const applyOptions = (result: (string | OptItem)[]) => {
|
||||||
|
console.log({ result });
|
||||||
local.options = result.map((item) => {
|
local.options = result.map((item) => {
|
||||||
if (typeof item === "string") return { value: item, label: item };
|
if (typeof item === "string") return { value: item, label: item };
|
||||||
return item;
|
return item;
|
||||||
|
|
@ -328,7 +341,47 @@ export const Typeahead: FC<{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
// Debounce effect
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDebouncedTerm(searchTerm); // Update debounced term after 1 second
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer); // Clear timeout if user types again
|
||||||
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
// Function to handle search
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedTerm) {
|
||||||
|
performSearch(debouncedTerm);
|
||||||
|
}
|
||||||
|
}, [debouncedTerm]);
|
||||||
|
|
||||||
|
const performSearch = (value: any) => {
|
||||||
|
console.log("Searching for:", value);
|
||||||
|
if (typeof onSelect === "function") {
|
||||||
|
const result = onSelect({
|
||||||
|
search: value,
|
||||||
|
item: {
|
||||||
|
label: value,
|
||||||
|
value: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
local.value.push(result);
|
||||||
|
local.render();
|
||||||
|
|
||||||
|
if (typeof onChange === "function") {
|
||||||
|
onChange(local.value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Lakukan pencarian, panggil API, atau filter data di sini
|
||||||
|
};
|
||||||
const resetSearch = () => {
|
const resetSearch = () => {
|
||||||
local.search.searching = false;
|
local.search.searching = false;
|
||||||
local.search.input = "";
|
local.search.input = "";
|
||||||
|
|
@ -372,6 +425,7 @@ export const Typeahead: FC<{
|
||||||
);
|
);
|
||||||
let inputval = local.search.input;
|
let inputval = local.search.input;
|
||||||
|
|
||||||
|
// console.log("search-add -> ", value?.[0], local.open, local.value?.length);
|
||||||
if (!local.open && local.mode === "single" && local.value?.length > 0) {
|
if (!local.open && local.mode === "single" && local.value?.length > 0) {
|
||||||
const found = options.find((e) => e.value === local.value[0]);
|
const found = options.find((e) => e.value === local.value[0]);
|
||||||
if (found) {
|
if (found) {
|
||||||
|
|
@ -380,11 +434,23 @@ export const Typeahead: FC<{
|
||||||
inputval = local.value[0];
|
inputval = local.value[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
if (allow_new && local.open) {
|
||||||
|
console.log(local);
|
||||||
|
local.search.input = local.value[0];
|
||||||
|
local.render();
|
||||||
|
}
|
||||||
|
}, [local.open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row flex-grow w-full relative">
|
<div className="flex flex-row flex-grow w-full relative">
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
local.mode === "single" ? "cursor-pointer" : "cursor-text",
|
allow_new
|
||||||
|
? "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 ",
|
"text-black flex relative flex-wrap py-0 items-center w-full h-full flex-1 ",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
@ -441,8 +507,10 @@ export const Typeahead: FC<{
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
<TypeaheadOptions
|
<TypeaheadOptions
|
||||||
|
fitur={fitur}
|
||||||
popup={true}
|
popup={true}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
console.log("OPEN");
|
||||||
if (!open) {
|
if (!open) {
|
||||||
local.select = null;
|
local.select = null;
|
||||||
}
|
}
|
||||||
|
|
@ -491,7 +559,10 @@ export const Typeahead: FC<{
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="single flex-1 flex-grow flex flex-row cursor-pointer"
|
className={cx(
|
||||||
|
allow_new ? "cursor-text" : "cursor-pointer",
|
||||||
|
"single flex-1 flex-grow flex flex-row"
|
||||||
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
|
|
@ -500,9 +571,12 @@ export const Typeahead: FC<{
|
||||||
loadOptions();
|
loadOptions();
|
||||||
local.open = true;
|
local.open = true;
|
||||||
local.render();
|
local.render();
|
||||||
|
// if (allow_new) {
|
||||||
|
// local.search.input = inputval;
|
||||||
|
// local.render();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local.mode === "single") {
|
if (local.mode === "single") {
|
||||||
if (input && input.current) input.current.select();
|
if (input && input.current) input.current.select();
|
||||||
}
|
}
|
||||||
|
|
@ -531,7 +605,9 @@ export const Typeahead: FC<{
|
||||||
|
|
||||||
local.search.searching = true;
|
local.search.searching = true;
|
||||||
local.render();
|
local.render();
|
||||||
|
if (allow_new) {
|
||||||
|
setSearchTerm(val);
|
||||||
|
}
|
||||||
if (local.search.searching) {
|
if (local.search.searching) {
|
||||||
if (local.local_search) {
|
if (local.local_search) {
|
||||||
if (!local.loaded) {
|
if (!local.loaded) {
|
||||||
|
|
@ -612,7 +688,7 @@ export const Typeahead: FC<{
|
||||||
</TypeaheadOptions>
|
</TypeaheadOptions>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{local.mode === "single" && (
|
{local.mode === "single" && fitur !== "search-add" && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
import { Popover } from "../../Popover/Popover";
|
import { Popover } from "../../Popover/Popover";
|
||||||
|
import { ButtonBetter } from "../../ui/button";
|
||||||
|
|
||||||
export type OptionItem = { value: string; label: string };
|
export type OptionItem = { value: string; label: string };
|
||||||
export const TypeaheadOptions: FC<{
|
export const TypeaheadOptions: FC<{
|
||||||
|
|
@ -20,7 +21,8 @@ export const TypeaheadOptions: FC<{
|
||||||
searching?: boolean;
|
searching?: boolean;
|
||||||
searchText?: string;
|
searchText?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
isMulti?: boolean
|
isMulti?: boolean;
|
||||||
|
fitur?: "search-add";
|
||||||
}> = ({
|
}> = ({
|
||||||
popup,
|
popup,
|
||||||
children,
|
children,
|
||||||
|
|
@ -33,7 +35,9 @@ export const TypeaheadOptions: FC<{
|
||||||
searching,
|
searching,
|
||||||
searchText,
|
searchText,
|
||||||
showEmpty,
|
showEmpty,
|
||||||
width,isMulti
|
width,
|
||||||
|
isMulti,
|
||||||
|
fitur,
|
||||||
}) => {
|
}) => {
|
||||||
if (!popup) return children;
|
if (!popup) return children;
|
||||||
const local = useLocal({
|
const local = useLocal({
|
||||||
|
|
@ -53,7 +57,7 @@ export const TypeaheadOptions: FC<{
|
||||||
`,
|
`,
|
||||||
css`
|
css`
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow: auto
|
overflow: auto;
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -88,6 +92,40 @@ export const TypeaheadOptions: FC<{
|
||||||
<>
|
<>
|
||||||
{options.length === 0 && (
|
{options.length === 0 && (
|
||||||
<div className="p-4 w-full text-center text-md text-slate-400">
|
<div className="p-4 w-full text-center text-md text-slate-400">
|
||||||
|
{fitur === "search-add" ? (
|
||||||
|
<ButtonBetter
|
||||||
|
variant={"outline"}
|
||||||
|
className="flex flex-row gap-x-2"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeMiterlimit={10}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
d="M6 12h12m-6 6V6"
|
||||||
|
></path>
|
||||||
|
</svg>{" "}
|
||||||
|
<span>
|
||||||
|
Add{" "}
|
||||||
|
<span
|
||||||
|
className={css`
|
||||||
|
font-style: italic;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
"{searchText}"
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ButtonBetter>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{!searchText ? (
|
{!searchText ? (
|
||||||
<>— Empty —</>
|
<>— Empty —</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -104,6 +142,8 @@ export const TypeaheadOptions: FC<{
|
||||||
not found
|
not found
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue