first commit
This commit is contained in:
416
src/components/FormItems/FormApiBasedAutoComplete.tsx
Normal file
416
src/components/FormItems/FormApiBasedAutoComplete.tsx
Normal file
@@ -0,0 +1,416 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useApiRequest } from "../../utils/useApiRequest";
|
||||
import AutoComplete from "../AutoComplete/AutoComplete";
|
||||
import { getFaPermissions } from "../../utils/getFaPermissions";
|
||||
import { getNestedValue } from "../../utils/getNestedValue";
|
||||
|
||||
type FormEnterLocationsProps = {
|
||||
title: string;
|
||||
api: string;
|
||||
error?: boolean;
|
||||
errorMessage?: any;
|
||||
multiple?: boolean;
|
||||
onChange?: (data: any) => void;
|
||||
onChangeValue?: (data: any) => void;
|
||||
keyField?: string;
|
||||
secondaryKey?: string | string[];
|
||||
tertiaryKey?: string | string[];
|
||||
valueField?: string | string[];
|
||||
valueField2?: string | string[];
|
||||
valueField3?: string | string[];
|
||||
filterAddress?: string[];
|
||||
filterValue?: string[];
|
||||
defaultKey?: string | number | any[];
|
||||
valueTemplate?: string;
|
||||
valueTemplateProps?: Array<{ [key: string]: "string" | "number" }>;
|
||||
groupBy?: string | string[];
|
||||
groupFunction?: (item: any) => string;
|
||||
selectField?: boolean;
|
||||
};
|
||||
|
||||
export const FormApiBasedAutoComplete = ({
|
||||
title,
|
||||
api,
|
||||
error,
|
||||
errorMessage,
|
||||
onChange,
|
||||
onChangeValue,
|
||||
keyField = "id",
|
||||
secondaryKey,
|
||||
tertiaryKey,
|
||||
valueField = "name",
|
||||
valueField2,
|
||||
valueField3,
|
||||
defaultKey,
|
||||
multiple = false,
|
||||
filterValue,
|
||||
filterAddress,
|
||||
valueTemplate,
|
||||
valueTemplateProps,
|
||||
groupBy,
|
||||
groupFunction,
|
||||
selectField = false,
|
||||
}: FormEnterLocationsProps) => {
|
||||
const [data, setData] = useState<any>([]);
|
||||
const [selectedKeys, setSelectedKeys] = useState<(string | number)[]>([]);
|
||||
const [groupedItemsMap, setGroupedItemsMap] = useState<
|
||||
Map<string | number, any[]>
|
||||
>(new Map());
|
||||
|
||||
const formatValue = (value: any, fieldKey: string) => {
|
||||
if (!valueTemplate || !valueTemplateProps) return value;
|
||||
|
||||
const templateProp = valueTemplateProps.find((prop) => prop[fieldKey]);
|
||||
if (!templateProp) return value;
|
||||
|
||||
const fieldType = templateProp[fieldKey];
|
||||
|
||||
if (fieldType === "number") {
|
||||
const numValue = typeof value === "number" ? value : Number(value);
|
||||
return !isNaN(numValue) ? numValue.toLocaleString() : String(value);
|
||||
} else if (fieldType === "string") {
|
||||
let result = String(value);
|
||||
if (typeof value === "string" && value.includes(",")) {
|
||||
result = value.replace(/,/g, "");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const { data: apiData } = useApiRequest({
|
||||
api: api,
|
||||
method: "get",
|
||||
params: { page: 1, page_size: 1000 },
|
||||
queryKey: [api],
|
||||
disableBackdrop: defaultKey ? true : false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (apiData?.results) {
|
||||
let data;
|
||||
|
||||
if (filterAddress && filterValue) {
|
||||
data = apiData.results?.filter(
|
||||
(item: any) =>
|
||||
!filterValue.includes(getNestedValue(item, filterAddress))
|
||||
);
|
||||
} else {
|
||||
data = apiData.results;
|
||||
}
|
||||
const d = data?.map((option: any) => ({
|
||||
key: option[keyField],
|
||||
secondaryKey: secondaryKey
|
||||
? typeof secondaryKey === "string"
|
||||
? option[secondaryKey]
|
||||
: getNestedValue(option, secondaryKey)
|
||||
: undefined,
|
||||
tertiaryKey: tertiaryKey
|
||||
? typeof tertiaryKey === "string"
|
||||
? option[tertiaryKey]
|
||||
: getNestedValue(option, tertiaryKey)
|
||||
: undefined,
|
||||
value: valueTemplate
|
||||
? valueTemplate
|
||||
.replace(
|
||||
/v1/g,
|
||||
formatValue(
|
||||
valueField === "page"
|
||||
? getFaPermissions(option[valueField])
|
||||
: typeof valueField === "string"
|
||||
? option[valueField]
|
||||
: getNestedValue(option, valueField),
|
||||
"v1"
|
||||
)
|
||||
)
|
||||
.replace(
|
||||
/v2/g,
|
||||
formatValue(
|
||||
valueField2
|
||||
? typeof valueField2 === "string"
|
||||
? option[valueField2]
|
||||
: getNestedValue(option, valueField2)
|
||||
: "",
|
||||
"v2"
|
||||
)
|
||||
)
|
||||
.replace(
|
||||
/v3/g,
|
||||
formatValue(
|
||||
valueField3
|
||||
? typeof valueField3 === "string"
|
||||
? option[valueField3]
|
||||
: getNestedValue(option, valueField3)
|
||||
: "",
|
||||
"v3"
|
||||
)
|
||||
)
|
||||
: `${
|
||||
valueField === "page"
|
||||
? getFaPermissions(option[valueField])
|
||||
: typeof valueField === "string"
|
||||
? option[valueField]
|
||||
: getNestedValue(option, valueField)
|
||||
} ${
|
||||
valueField2
|
||||
? " - " +
|
||||
(typeof valueField2 === "string"
|
||||
? option[valueField2]
|
||||
: getNestedValue(option, valueField2))
|
||||
: ""
|
||||
} ${
|
||||
valueField3
|
||||
? "(" +
|
||||
(typeof valueField3 === "string"
|
||||
? option[valueField3]
|
||||
: getNestedValue(option, valueField3)) +
|
||||
")"
|
||||
: ""
|
||||
}`,
|
||||
groupValue: groupBy
|
||||
? typeof groupBy === "string"
|
||||
? option[groupBy]
|
||||
: getNestedValue(option, groupBy)
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
let finalData = d;
|
||||
|
||||
if (groupBy) {
|
||||
const grouped = new Map<string | number, typeof d>();
|
||||
|
||||
d.forEach((item: any) => {
|
||||
const groupKey = item.groupValue ?? "";
|
||||
if (!grouped.has(groupKey)) {
|
||||
grouped.set(groupKey, []);
|
||||
}
|
||||
grouped.get(groupKey)!.push(item);
|
||||
});
|
||||
|
||||
setGroupedItemsMap(grouped);
|
||||
|
||||
finalData = [];
|
||||
grouped.forEach((items, groupKey) => {
|
||||
finalData.push({
|
||||
key: `__group__${groupKey}`,
|
||||
value: groupFunction ? groupFunction(groupKey) : String(groupKey),
|
||||
disabled: true,
|
||||
isGroupHeader: true,
|
||||
originalGroupKey: groupKey,
|
||||
});
|
||||
finalData.push(...items);
|
||||
});
|
||||
} else {
|
||||
setGroupedItemsMap(new Map());
|
||||
}
|
||||
|
||||
setData(finalData);
|
||||
|
||||
const actualDataItems = finalData.filter(
|
||||
(item: any) => !item.isGroupHeader && !item.disabled
|
||||
);
|
||||
|
||||
if (defaultKey !== undefined && defaultKey !== null) {
|
||||
if (multiple) {
|
||||
if (Array.isArray(defaultKey)) {
|
||||
if (defaultKey.length === 0) {
|
||||
setSelectedKeys([]);
|
||||
} else {
|
||||
const defaultIds = defaultKey.map((item: any) =>
|
||||
typeof item === "object" ? item[keyField] : item
|
||||
);
|
||||
const defaultItems = actualDataItems.filter((item: any) =>
|
||||
defaultIds.includes(item.key)
|
||||
);
|
||||
setSelectedKeys(defaultItems.map((item: any) => item.key));
|
||||
if (onChange) {
|
||||
if (secondaryKey) {
|
||||
onChange({
|
||||
key1: defaultItems.map((item: any) => item.key),
|
||||
key2: defaultItems.map((item: any) => item.secondaryKey),
|
||||
...(tertiaryKey
|
||||
? {
|
||||
key3: defaultItems.map(
|
||||
(item: any) => item.tertiaryKey
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
} else {
|
||||
onChange(defaultItems.map((item: any) => item.key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!Array.isArray(defaultKey)) {
|
||||
const keyToFind =
|
||||
typeof defaultKey === "object"
|
||||
? defaultKey[keyField]
|
||||
: defaultKey;
|
||||
const defaultItem = actualDataItems.find(
|
||||
(item: any) => item.key === keyToFind
|
||||
);
|
||||
if (defaultItem) {
|
||||
setSelectedKeys([keyToFind]);
|
||||
if (onChange) {
|
||||
if (secondaryKey) {
|
||||
onChange({
|
||||
key1: defaultItem.key,
|
||||
key2: defaultItem.secondaryKey,
|
||||
...(tertiaryKey ? { key3: defaultItem.tertiaryKey } : {}),
|
||||
});
|
||||
} else {
|
||||
onChange(keyToFind);
|
||||
}
|
||||
}
|
||||
if (onChangeValue) {
|
||||
onChangeValue({
|
||||
key1: defaultItem.key,
|
||||
key2: defaultItem.secondaryKey,
|
||||
...(tertiaryKey ? { key3: defaultItem.tertiaryKey } : {}),
|
||||
value: defaultItem.value.trim(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [apiData]);
|
||||
|
||||
const handleGroupHeaderClick = (groupKey: string | number) => {
|
||||
if (!multiple || !groupBy) return;
|
||||
|
||||
const groupItems = groupedItemsMap.get(groupKey) || [];
|
||||
if (groupItems.length === 0) return;
|
||||
|
||||
const groupItemKeys = groupItems.map((item: any) => item.key);
|
||||
const allGroupItemsSelected = groupItemKeys.every((key) =>
|
||||
selectedKeys.includes(key)
|
||||
);
|
||||
|
||||
let newSelectedKeys: (string | number)[];
|
||||
|
||||
if (allGroupItemsSelected) {
|
||||
newSelectedKeys = selectedKeys.filter(
|
||||
(key) => !groupItemKeys.includes(key)
|
||||
);
|
||||
} else {
|
||||
const newKeys = groupItemKeys.filter(
|
||||
(key) => !selectedKeys.includes(key)
|
||||
);
|
||||
newSelectedKeys = [...selectedKeys, ...newKeys];
|
||||
}
|
||||
|
||||
setSelectedKeys(newSelectedKeys);
|
||||
|
||||
if (!onChange) return;
|
||||
|
||||
const selectedItems = data.filter(
|
||||
(item: any) =>
|
||||
newSelectedKeys.includes(item.key) &&
|
||||
!item.isGroupHeader &&
|
||||
!item.disabled
|
||||
);
|
||||
|
||||
if (secondaryKey) {
|
||||
onChange(
|
||||
selectedItems.map((item: any) => ({
|
||||
key1: item.key,
|
||||
key2: item.secondaryKey,
|
||||
...(tertiaryKey ? { key3: item.tertiaryKey } : {}),
|
||||
}))
|
||||
);
|
||||
if (onChangeValue) {
|
||||
onChangeValue(selectedItems.map((item: any) => item.value.trim()));
|
||||
}
|
||||
} else {
|
||||
onChange(newSelectedKeys);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AutoComplete
|
||||
multiselect={multiple}
|
||||
selectField={selectField}
|
||||
data={data}
|
||||
selectedKeys={selectedKeys}
|
||||
onChange={(newSelectedKeys) => {
|
||||
setSelectedKeys(newSelectedKeys);
|
||||
|
||||
if (!onChange) return;
|
||||
|
||||
if (multiple) {
|
||||
if (secondaryKey) {
|
||||
const selectedItems = data.filter(
|
||||
(item: any) =>
|
||||
newSelectedKeys.includes(item.key) &&
|
||||
!item.isGroupHeader &&
|
||||
!item.disabled
|
||||
);
|
||||
|
||||
onChange(
|
||||
selectedItems.map((item: any) => ({
|
||||
key1: item.key,
|
||||
key2: item.secondaryKey,
|
||||
...(tertiaryKey ? { key3: item.tertiaryKey } : {}),
|
||||
}))
|
||||
);
|
||||
if (onChangeValue) {
|
||||
onChangeValue(
|
||||
selectedItems.map((item: any) => item.value.trim())
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const validKeys = newSelectedKeys.filter(
|
||||
(key) => !String(key).startsWith("__group__")
|
||||
);
|
||||
onChange(validKeys);
|
||||
}
|
||||
} else {
|
||||
if (secondaryKey) {
|
||||
const selectedItem = data.find(
|
||||
(item: any) =>
|
||||
item.key === newSelectedKeys[0] &&
|
||||
!item.isGroupHeader &&
|
||||
!item.disabled
|
||||
);
|
||||
if (onChangeValue) {
|
||||
onChangeValue({
|
||||
value: selectedItem?.value.trim() ?? "",
|
||||
key1: selectedItem?.key ?? "",
|
||||
key2: selectedItem?.secondaryKey ?? "",
|
||||
...(tertiaryKey
|
||||
? { key3: selectedItem?.tertiaryKey ?? "" }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedItem) {
|
||||
onChange({
|
||||
key1: selectedItem.key,
|
||||
key2: selectedItem.secondaryKey,
|
||||
...(tertiaryKey ? { key3: selectedItem.tertiaryKey } : {}),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const validKey =
|
||||
newSelectedKeys[0] &&
|
||||
!String(newSelectedKeys[0]).startsWith("__group__")
|
||||
? newSelectedKeys[0]
|
||||
: "";
|
||||
onChange(validKey);
|
||||
}
|
||||
}
|
||||
}}
|
||||
title={title}
|
||||
error={error}
|
||||
helperText={errorMessage}
|
||||
onGroupHeaderClick={handleGroupHeaderClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user