Files
formbricks/apps/web/modules/ee/contacts/components/contact-data-view.tsx
2026-01-06 12:31:20 +00:00

154 lines
4.8 KiB
TypeScript

"use client";
import { debounce } from "lodash";
import dynamic from "next/dynamic";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import toast from "react-hot-toast";
import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key";
import { TEnvironment } from "@formbricks/types/environment";
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
import { getContactsAction } from "../actions";
import { TContactTableData, TContactWithAttributes } from "../types/contact";
const ContactsTableDynamic = dynamic(() => import("./contacts-table").then((mod) => mod.ContactsTable), {
loading: () => <LoadingSpinner />,
ssr: false,
});
interface ContactDataViewProps {
environment: TEnvironment;
contactAttributeKeys: TContactAttributeKey[];
initialContacts: TContactWithAttributes[];
itemsPerPage: number;
isReadOnly: boolean;
hasMore: boolean;
isQuotasAllowed: boolean;
}
export const ContactDataView = ({
environment,
itemsPerPage,
contactAttributeKeys,
isReadOnly,
hasMore: initialHasMore,
initialContacts,
isQuotasAllowed,
}: ContactDataViewProps) => {
const [contacts, setContacts] = useState<TContactWithAttributes[]>([...initialContacts]);
const [hasMore, setHasMore] = useState<boolean>(initialHasMore);
const [loadingNextPage, setLoadingNextPage] = useState<boolean>(false);
const [searchValue, setSearchValue] = useState<string>("");
const [isDataLoaded, setIsDataLoaded] = useState(true);
const isFirstRender = useRef(true);
const environmentAttributes = useMemo(() => {
return contactAttributeKeys.filter(
(attr) => !["userId", "email", "firstName", "lastName"].includes(attr.key)
);
}, [contactAttributeKeys]);
// Fetch contacts from offset 0 with current search value
const fetchContactsFromStart = useCallback(async () => {
setIsDataLoaded(false);
try {
setHasMore(true);
const contactsResponse = await getContactsAction({
environmentId: environment.id,
offset: 0,
searchValue,
});
if (contactsResponse?.data) {
setContacts(contactsResponse.data);
}
if (contactsResponse?.data && contactsResponse.data.length < itemsPerPage) {
setHasMore(false);
}
} catch (error) {
console.error("Error fetching contacts:", error);
toast.error("Error fetching contacts. Please try again.");
} finally {
setIsDataLoaded(true);
}
}, [environment.id, itemsPerPage, searchValue]);
useEffect(() => {
if (!isFirstRender.current) {
const debouncedFetchData = debounce(fetchContactsFromStart, 300);
debouncedFetchData();
return () => {
debouncedFetchData.cancel();
};
}
}, [fetchContactsFromStart]);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
}
}, []);
// Fetch next page of contacts
const fetchNextPage = async () => {
if (hasMore && !loadingNextPage) {
setLoadingNextPage(true);
try {
const contactsResponse = await getContactsAction({
environmentId: environment.id,
offset: contacts.length,
searchValue,
});
const contactsData = contactsResponse?.data || [];
setContacts((prevContacts) => [...prevContacts, ...contactsData]);
if (contactsData.length < itemsPerPage) {
setHasMore(false);
}
} catch (error) {
console.error("Error fetching next page of contacts:", error);
} finally {
setLoadingNextPage(false);
}
}
};
// Delete selected contacts
const updateContactList = (contactIds: string[]) => {
setContacts((prevContacts) => prevContacts.filter((contact) => !contactIds.includes(contact.id)));
};
// Prepare data for the ContactTable component
const contactsTableData: TContactTableData[] = useMemo(() => {
return contacts.map((contact) => ({
id: contact.id,
userId: contact.attributes.userId ?? "",
email: contact.attributes.email ?? "",
firstName: contact.attributes.firstName ?? "",
lastName: contact.attributes.lastName ?? "",
attributes: (environmentAttributes ?? []).map((attr) => ({
key: attr.key,
name: attr.name,
value: contact.attributes[attr.key] ?? "",
})),
}));
}, [contacts, environmentAttributes]);
return (
<ContactsTableDynamic
data={contactsTableData}
fetchNextPage={fetchNextPage}
hasMore={hasMore}
isDataLoaded={isFirstRender.current ? true : isDataLoaded}
updateContactList={updateContactList}
environmentId={environment.id}
searchValue={searchValue}
setSearchValue={setSearchValue}
isReadOnly={isReadOnly}
isQuotasAllowed={isQuotasAllowed}
refreshContacts={fetchContactsFromStart}
/>
);
};