diff --git a/src/components/CippComponents/CippAutocomplete.jsx b/src/components/CippComponents/CippAutocomplete.jsx index 8cef03878f94..741fa8deec11 100644 --- a/src/components/CippComponents/CippAutocomplete.jsx +++ b/src/components/CippComponents/CippAutocomplete.jsx @@ -1,9 +1,9 @@ import { ArrowDropDown } from "@mui/icons-material"; import { Autocomplete, CircularProgress, createFilterOptions, TextField } from "@mui/material"; -import { ApiGetCall } from "../../api/ApiCall"; import { useEffect, useState } from "react"; import { useSettings } from "../../hooks/use-settings"; import { getCippError } from "../../utils/get-cipp-error"; +import { ApiGetCallWithPagination } from "../../api/ApiCall"; export const CippAutoComplete = (props) => { const { @@ -25,15 +25,29 @@ export const CippAutoComplete = (props) => { sx, ...other } = props; - const filter = createFilterOptions({ stringify: (option) => JSON.stringify(option) }); + const [usedOptions, setUsedOptions] = useState(options); const [getRequestInfo, setGetRequestInfo] = useState({ url: "", waiting: false, queryKey: "" }); + const filter = createFilterOptions({ + stringify: (option) => JSON.stringify(option), + }); - const actionGetRequest = ApiGetCall({ + // This is our paginated call + const actionGetRequest = ApiGetCallWithPagination({ ...getRequestInfo, }); const currentTenant = api?.tenantFilter ? api.tenantFilter : useSettings().currentTenant; + useEffect(() => { + if (actionGetRequest.isSuccess && !actionGetRequest.isFetching) { + const lastPage = actionGetRequest.data?.pages[actionGetRequest.data.pages.length - 1]; + const nextLinkExists = lastPage?.Metadata?.nextLink; + if (nextLinkExists) { + actionGetRequest.fetchNextPage(); + } + } + }, [actionGetRequest.data?.pages?.length, actionGetRequest.isFetching, api?.queryKey]); + useEffect(() => { if (api) { setGetRequestInfo({ @@ -46,52 +60,87 @@ export const CippAutoComplete = (props) => { queryKey: api.queryKey, }); } + }, [api, currentTenant]); + // After the data is fetched, combine and map it + useEffect(() => { if (actionGetRequest.isSuccess) { - const dataToMap = api.dataKey ? actionGetRequest.data?.[api.dataKey] : actionGetRequest.data; - if (!Array.isArray(dataToMap)) { + // E.g., allPages is an array of pages returned by the pagination + const allPages = actionGetRequest.data?.pages || []; + + // Helper to get nested data if you have something like "response.data.items" + const getNestedValue = (obj, path) => { + if (!path) return obj; + const keys = path.split("."); + let result = obj; + for (const key of keys) { + if (result && typeof result === "object" && key in result) { + result = result[key]; + } else { + return undefined; + } + } + return result; + }; + + // Flatten the results from all pages + const combinedResults = allPages.flatMap((page) => { + const nestedData = getNestedValue(page, api?.dataKey); + return nestedData !== undefined ? nestedData : []; + }); + + if (!Array.isArray(combinedResults)) { setUsedOptions([ { label: "Error: The API returned data we cannot map to this field", - value: "Error: The API returned data we cannot map to this field", + value: "Error", }, ]); - return; + } else { + // Convert each item into your { label, value, addedFields } shape + const convertedOptions = combinedResults.map((option) => { + const addedFields = {}; + if (api?.addedField) { + Object.keys(api.addedField).forEach((key) => { + addedFields[key] = option[api.addedField[key]]; + }); + } + + return { + label: + typeof api?.labelField === "function" + ? api.labelField(option) + : option[api?.labelField], + value: + typeof api?.valueField === "function" + ? api.valueField(option) + : option[api?.valueField], + addedFields, + }; + }); + setUsedOptions(convertedOptions); } - const convertedOptions = dataToMap.map((option) => { - const addedFields = {}; - if (api.addedField) { - Object.keys(api.addedField).forEach((key) => { - addedFields[key] = option[api.addedField[key]]; - }); - } - return { - label: - typeof api.labelField === "function" ? api.labelField(option) : option[api.labelField], - value: - typeof api.valueField === "function" ? api.valueField(option) : option[api.valueField], - addedFields: addedFields, - }; - }); - setUsedOptions(convertedOptions); } + if (actionGetRequest.isError) { setUsedOptions([{ label: getCippError(actionGetRequest.error), value: "error" }]); } - }, [api, actionGetRequest.data]); + }, [api, actionGetRequest.data, actionGetRequest.isSuccess, actionGetRequest.isError]); + const rand = Math.random().toString(36).substring(5); + return ( ) : ( ) } - isOptionEqualToValue={(option, value) => option.value === value.value} + isOptionEqualToValue={(option, val) => option.value === val.value} value={typeof value === "string" ? { label: value, value: value } : value} filterSelectedOptions disableClearable={disableClearable} @@ -100,12 +149,11 @@ export const CippAutoComplete = (props) => { filterOptions={(options, params) => { const filtered = filter(options, params); const isExisting = - options !== undefined && - options !== null && options?.length > 0 && - options?.some( + options.some( (option) => params.inputValue === option.value || params.inputValue === option.label ); + if (params.inputValue !== "" && creatable && !isExisting) { filtered.push({ label: `Add option: "${params.inputValue}"`, @@ -126,6 +174,7 @@ export const CippAutoComplete = (props) => { onChange={(event, newValue) => { if (Array.isArray(newValue)) { newValue = newValue.map((item) => { + // If user typed a new item or missing label if (item?.manual || !item?.label) { item = { label: item?.label ? item.value : item,