@mikezimm/fps-library-v2
Version:
Library of reusable typescript/javascript functions, interfaces and constants
155 lines • 10 kB
JavaScript
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