UNPKG

@chayns-components/person-finder

Version:

A set of beautiful React components for developing your own applications with chayns.

321 lines (320 loc) 10.3 kB
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import throttle from 'lodash.throttle'; import { LoadingState, PersonFinderFilterTypes, Priority } from '../types/personFinder'; import { getFriends } from '../api/friends/get'; import { postFriends } from '../api/friends/post'; import { deleteFriends } from '../api/friends/delete'; import { filterDataByKeys, loadData } from '../utils/personFinder'; import { getUACGroups, getUsersByGroups } from '../utils/uac'; const THROTTLE_INTERVAL = 500; export const PersonFinderContext = /*#__PURE__*/createContext({ data: undefined, updateData: undefined, friends: undefined, addFriend: undefined, removeFriend: undefined, activeFilter: undefined, updateActiveFilter: undefined, search: undefined, updateSearch: undefined, loadMore: undefined, loadingState: undefined, updateLoadingState: undefined, tags: undefined, setTags: undefined }); PersonFinderContext.displayName = 'PersonFinderContext'; export const usePersonFinder = () => useContext(PersonFinderContext); const PersonFinderProvider = ({ children, friendsPriority, filterTypes, defaultEntries, excludedEntryIds, shouldShowOwnUser = false, uacFilter }) => { const [data, setData] = useState(); const [friends, setFriends] = useState(); const [uacUsers, setUacUsers] = useState(); const [activeFilter, setActiveFilter] = useState(); const [search, setSearch] = useState(''); const [tags, setTags] = useState(defaultEntries?.map(({ id, name }) => ({ id, text: name })) ?? []); const [loadingState, setLoadingState] = useState({ [PersonFinderFilterTypes.PERSON]: LoadingState.None, [PersonFinderFilterTypes.SITE]: LoadingState.None }); const dataRef = useRef(); const updateActiveFilter = useCallback(filter => { setActiveFilter(filter); }, []); const updateData = useCallback((key, newData) => { setData(prevState => ({ ...prevState, [key]: newData })); }, []); const appendData = useCallback((key, newData) => { setData(prevState => { const oldEntries = prevState && prevState[key]?.entries ? prevState[key]?.entries : []; return { ...prevState, [key]: { ...newData, entries: [...oldEntries, ...newData.entries] } }; }); }, []); const updateLoadingState = useCallback((key, state) => { setLoadingState(prev => ({ ...prev, [key]: state })); }, []); const updateSearch = useCallback(value => { setSearch(value); }, []); const loadMore = useCallback(key => { updateLoadingState(key, LoadingState.Pending); const current = data?.[key]; if (!current) { updateLoadingState(key, LoadingState.Error); return; } void loadData({ searchString: search ?? '', filter: [key], skipMap: { [key]: current.skip } }).then(result => { const newData = result?.[key]; if (newData) { appendData(key, newData); } }).finally(() => { updateLoadingState(key, LoadingState.Success); }); }, [updateLoadingState, data, search, appendData]); const addFriend = useCallback(personId => { void postFriends(personId).then(result => { if (result) { const { firstName, lastName, verificationState } = result; setFriends(prev => [...(prev ?? []), { id: personId, isVerified: verificationState === 1, commonSites: 0, firstName, lastName, type: PersonFinderFilterTypes.PERSON }]); } }); }, []); const removeFriend = useCallback(personId => { void deleteFriends(personId).then(wasSuccessful => { if (wasSuccessful) { setFriends(prev => prev?.filter(({ id }) => id !== personId)); } }); }, []); useEffect(() => { if (!filterTypes.includes(PersonFinderFilterTypes.PERSON)) { return; } void getFriends().then(result => { if (result) { setFriends(result.map(({ personId, firstName, lastName, verificationState }) => ({ lastName, firstName, id: personId, commonSites: 0, isVerified: verificationState === 1, type: PersonFinderFilterTypes.PERSON }))); } }); }, [filterTypes]); const latestArgsRef = useRef(null); const latestHandledRequestRef = useRef(0); const throttledRequest = useRef(throttle(async () => { const args = latestArgsRef.current; if (!args) return; const { search: searchString, filter } = args; const requestTimestamp = Date.now(); filter.forEach(key => { updateLoadingState(key, LoadingState.Pending); }); const result = await loadData({ searchString, filter, skipMap: {} }); if (requestTimestamp < latestHandledRequestRef.current) { return; } latestHandledRequestRef.current = requestTimestamp; if (!result) return; Object.entries(result).forEach(([keyString, value]) => { const key = keyString; if (key === PersonFinderFilterTypes.PERSON && friendsPriority === Priority.HIGH && friends) { const friendIds = new Set(friends.map(f => f.id)); const serverFriendEntries = value.entries.filter(entry => friendIds.has(entry.id)); const serverFriendIds = new Set(serverFriendEntries.map(f => f.id)); const missingFriends = friends.filter(f => !serverFriendIds.has(f.id)).filter(f => f.firstName?.toLowerCase().includes(searchString.toLowerCase()) || f.lastName?.toLowerCase().includes(searchString.toLowerCase())); const otherEntries = value.entries.filter(entry => !friendIds.has(entry.id)); updateData(key, { ...value, entries: [...serverFriendEntries, ...missingFriends, ...otherEntries] }); } else { updateData(key, value); } updateLoadingState(key, value.entries.length === 0 ? LoadingState.Error : LoadingState.Success); }); }, THROTTLE_INTERVAL, { leading: false, trailing: true })).current; useEffect(() => { dataRef.current = data; }, [data]); const searchData = useCallback(({ filter }) => { const tmpData = dataRef.current; filter.forEach(key => { updateLoadingState(key, LoadingState.Pending); if (tmpData && tmpData[key]) { // Add all Types that are not searched by a request const entries = tmpData[key].entries; const filteredEntries = entries.filter(({ name }) => name.toLowerCase().includes(search.toLowerCase())); updateData(key, { entries: filteredEntries, searchString: search, count: filteredEntries.length, skip: filteredEntries.length }); updateLoadingState(key, filteredEntries.length === 0 ? LoadingState.Error : LoadingState.Success); } }); }, [search, updateData, updateLoadingState]); const searchLocal = useCallback(() => { if (search.length < 3) { return; } updateLoadingState(PersonFinderFilterTypes.PERSON, LoadingState.Pending); const searchedUsers = []; uacUsers?.forEach(entry => { if (entry.firstName.toLowerCase().includes(search.toLowerCase()) || entry.lastName.toLowerCase().includes(search.toLowerCase()) || entry.id.toLowerCase().includes(search.toLowerCase())) { searchedUsers.push(entry); } }); updateData(PersonFinderFilterTypes.PERSON, { entries: searchedUsers, searchString: search, count: searchedUsers.length, skip: searchedUsers.length }); updateLoadingState(PersonFinderFilterTypes.PERSON, searchedUsers.length === 0 ? LoadingState.Error : LoadingState.Success); }, [search, uacUsers, updateData, updateLoadingState]); useEffect(() => { if (!search) return; const active = activeFilter ?? filterTypes; if (uacFilter) { searchLocal(); } else if (active?.includes(PersonFinderFilterTypes.UAC)) { searchData({ filter: [PersonFinderFilterTypes.UAC] }); } else { latestArgsRef.current = { search, filter: active }; throttledRequest(); } }, [filterTypes, search, activeFilter, friends, friendsPriority, updateData, updateLoadingState, throttledRequest, searchData, uacFilter, searchLocal]); useEffect(() => () => { throttledRequest.cancel(); }, [throttledRequest]); // load initial data useEffect(() => { if (uacFilter) { void getUsersByGroups(uacFilter).then(users => { setUacUsers(users); }); return; } if (filterTypes.includes(PersonFinderFilterTypes.UAC) && search === '') { void getUACGroups().then(result => { setData({ uac: { entries: result, searchString: '', skip: result.length, count: result.length } }); }); } if (friendsPriority === Priority.HIGH && filterTypes.includes(PersonFinderFilterTypes.PERSON) && friends && search === '') { setData({ person: { entries: friends, searchString: '', skip: friends.length, count: friends.length } }); } }, [filterTypes, friends, friendsPriority, search, uacFilter]); const providerValue = useMemo(() => ({ data: filterDataByKeys(data, activeFilter, { excludedEntryIds, shouldShowOwnUser }), updateData, activeFilter, updateActiveFilter, friends, addFriend, removeFriend, search, updateSearch, loadMore, loadingState, updateLoadingState, setTags, tags }), [activeFilter, addFriend, data, excludedEntryIds, friends, loadMore, loadingState, removeFriend, search, shouldShowOwnUser, tags, updateActiveFilter, updateData, updateLoadingState, updateSearch]); return /*#__PURE__*/React.createElement(PersonFinderContext.Provider, { value: providerValue }, children); }; PersonFinderProvider.displayName = 'PersonFinderProvider'; export default PersonFinderProvider; //# sourceMappingURL=PersonFinderProvider.js.map