UNPKG

@mikezimm/fps-library-v2

Version:

Library of reusable typescript/javascript functions, interfaces and constants

155 lines 10 kB
import * as React from 'react'; import { useState, useEffect, useCallback, } from 'react'; import { CurrentSiteAbsolute } from '@mikezimm/fps-core-v7/lib/components/molecules/source-props/WindowLocationConstants'; import { getSiteUsersAPI } from '@mikezimm/fps-core-v7/lib/restAPIs/sites/users/getSiteUsersAPI'; import { CurrentOrigin } from '@mikezimm/fps-core-v7/lib/components/molecules/source-props/WindowLocationConstants'; import { getEmailFromLoginName } from '@mikezimm/fps-core-v7/lib/components/atoms/Users/getEmailFromLoginName'; import { createUsersSource } from '@mikezimm/fps-core-v7/lib/components/molecules/source-props/createSources/Users/createUsersSource'; import { createEmptyFpsUsersReturn } from '@mikezimm/fps-core-v7/lib/components/molecules/process-results/createEmptyFpsUsersReturn'; require('@mikezimm/fps-styles/dist/fps-People-Picker.css'); /** * 2025-01-23: Added these to remove auto-complete: https://github.com/mikezimm/pivottiles7/issues/450 */ const commonInputProps = { autocapitalize: "off", autocomplete: "off", "aria-autocomplete": "none", spellcheck: "false", }; const maxFailedAttempts = 10; const FpsPeoplePicker = ({ fpsSpService, key, label, description, labelStyles, className, onUsersFetched, sendSelectedUsers, debounceDelay = 200, // Default value for debounceDelay siteUrl = CurrentSiteAbsolute, // Default site URL size = 'L', preFilter = 'UserWithEmail', initialData = [], typeToShow = false, multiSelect = true, // Default to true for multi-select functionality disabled = false, maxToShow = 5, styles = {}, }) => { var _a; // States for managing user search, list of users, and selected users const [searchTerm, setSearchTerm] = useState(""); const [allUsers, setAllUsers] = useState(createEmptyFpsUsersReturn()); // Store all users const [filteredResults, setFilteredResults] = useState(createEmptyFpsUsersReturn()); const [loading, setLoading] = useState(false); const [fetchingMessage, setFetchingMessage] = useState(''); // Message for loading state const [selectedUsers, setSelectedUsers] = useState(initialData || []); // Selected users for multi-select /** * 2025-01-19: I added failedCount in an attempt to halt unwanted re-runing of fetchAllUsers. * However, being a callback function, it does not seem to recognize the failed count. * But for some reason, it is no longer preventing infinate loop callback so I'm not touching it. * And if you give it a sec and click on it again, it does seem to recognize the count and show it. * like: Failed 16 attempts... something is wrong ;| */ const [failedCount, setFailedCount] = useState(0); const fetchAllUsers = useCallback(async () => { if (failedCount === maxFailedAttempts) return; setLoading(true); setFetchingMessage("Fetching site users..."); const sourceProps = createUsersSource(`${siteUrl}`, fpsSpService); if (preFilter === 'Security') { sourceProps.restFilter = 'PrincipalType eq 4'; } else if (preFilter !== 'All') { // Only get what is considered 'Users' sourceProps.restFilter = 'PrincipalType eq 1'; } const results = await getSiteUsersAPI(sourceProps, false, true); // try { if (results.status === 'Success') { results.users = results.users.map((user) => { const imageUrl = `${CurrentOrigin}/_layouts/15/userphoto.aspx?size=${size || 'L'}&accountname=${user.Email ? user.Email : getEmailFromLoginName(user.LoginName)}`; return { ...user, imageUrl: imageUrl }; }); // Apply pre-filter for users with email if (preFilter === 'UserWithEmail') { results.users = results.users.filter((user) => (user.PrincipalType === 1 || user.PrincipalType === 2) && !!user.Email); } } setAllUsers(results); // Save all users to state setFilteredResults(results); // Initialize user list // Call the parent callback, if provided, to pass the users up if (onUsersFetched) { onUsersFetched(results); // This still sends all users initially } setLoading(false); setFetchingMessage(results.status !== 'Success' ? results.errorInfo.friendly : ''); // Hide the fetching message once loading is done if (results.status === 'Error') setFailedCount(prevFailedCount => prevFailedCount + 1); console.log(`fetchAllUsers:`, results.status, results); }, [siteUrl, size, onUsersFetched, preFilter]); // Debounced logic to filter users based on search term useEffect(() => { if (!searchTerm) { setFilteredResults(allUsers); // Show all users when search term is cleared return; } const handler = setTimeout(() => { // Filter users based on search term const filteredUsers = allUsers.users.filter(user => user.Title.toLowerCase().includes(searchTerm.toLowerCase()) || (user.Email && user.Email.toLowerCase().includes(searchTerm.toLowerCase()))); setFilteredResults({ ...allUsers, users: filteredUsers }); }, debounceDelay); return () => clearTimeout(handler); // Cleanup on searchTerm change }, [searchTerm, debounceDelay]); // }, [searchTerm, allUsers, debounceDelay]); // Trigger the fetch of users when the input is focused const handleFocus = async () => { if (allUsers.users.length === 0) { await fetchAllUsers(); // Fetch all users if not already fetched } }; // Handle checkbox change for multi-select functionality const handleCheckboxChange = (user) => { if (multiSelect) { // Add or remove user from selected list setSelectedUsers((prev) => { if (prev.some((u) => u.Id === user.Id)) { return prev.filter((u) => u.Id !== user.Id); } return [...prev, user]; // Add user to selected list }); } else { setSelectedUsers([user]); // Only allow single selection } }; // Remove a selected user from the list const removeSelectedUser = (userId) => { setSelectedUsers((prev) => prev.filter((user) => user.Id !== userId)); }; // Only send selected users when there is a change useEffect(() => { if (sendSelectedUsers) sendSelectedUsers(selectedUsers); // Send selected users whenever they change }, [selectedUsers]); return (React.createElement("div", { className: `fps-people-picker ${className}`, style: styles }, !label ? undefined : React.createElement("label", { className: 'fps-people-picker-label', style: labelStyles }, `${label}`), React.createElement("div", { className: "selected-users" }, selectedUsers.map((user) => (React.createElement("span", { key: user.Id, className: "selected-user" }, React.createElement("img", { src: user.imageUrl, alt: user.Title, className: "user-image" }), user.Title, React.createElement("button", { className: "remove-user-btn", onClick: () => removeSelectedUser(user.Id), disabled: disabled, style: { opacity: disabled ? 0.5 : 1, pointerEvents: disabled ? 'none' : 'auto' } }, "\u2715"))))), React.createElement("input", { type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), onFocus: handleFocus, placeholder: "Search for user by Title or Email...", className: "search-input", disabled: disabled, style: { opacity: disabled ? 0.5 : 1 }, ...commonInputProps }), loading && React.createElement("p", null, fetchingMessage), " ", React.createElement("ul", { className: "user-list", style: { opacity: disabled ? 0.7 : 1, pointerEvents: disabled ? 'none' : 'auto', // Prevent interactions when disabled } }, loading ? (React.createElement("p", null, "Fetching user list...")) : failedCount >= maxFailedAttempts ? (React.createElement("p", { style: { color: 'red' } }, "Failed ", failedCount, " attempts... something is wrong ;|")) : allUsers.status !== 'Success' && allUsers.status !== 'Unknown' ? (React.createElement("p", { style: { color: 'red' } }, (_a = allUsers.errorInfo) === null || _a === void 0 ? void 0 : _a.friendly)) : allUsers.users.length === 0 ? (React.createElement("p", null, "No users found.")) : (!fetchingMessage && (typeToShow === true && !searchTerm ? (React.createElement("p", null, "Type a name to search ", allUsers.users.length, " users")) : (React.createElement(React.Fragment, null, filteredResults.users.slice(0, maxToShow).map((user) => (React.createElement("li", { key: user.Id, className: "user-item" }, React.createElement("input", { type: "checkbox", checked: selectedUsers.some((u) => u.Id === user.Id), onChange: () => handleCheckboxChange(user), className: "user-checkbox" }), React.createElement("img", { src: user.imageUrl, alt: user.Title, className: "user-image" }), React.createElement("div", { title: `Email: ${user.Email || 'None'}` }, user.Title ? user.Title : user.Email)))), filteredResults.users.length > maxToShow && (React.createElement("p", null, "Showing ", maxToShow, " of ", allUsers.users.length, " users"))))))), description && React.createElement("div", { className: "fps-people-picker-description" }, description))); }; export default FpsPeoplePicker; //# sourceMappingURL=fps-PeoplePicker.js.map