diff --git a/samples/react-directory/assets/react-directory-withPaging.png b/samples/react-directory/assets/react-directory-withPaging.png new file mode 100644 index 000000000..e84ae863d Binary files /dev/null and b/samples/react-directory/assets/react-directory-withPaging.png differ diff --git a/samples/react-directory/src/webparts/directory/components/DirectoryHook.tsx b/samples/react-directory/src/webparts/directory/components/DirectoryHook.tsx new file mode 100644 index 000000000..c619cab2c --- /dev/null +++ b/samples/react-directory/src/webparts/directory/components/DirectoryHook.tsx @@ -0,0 +1,356 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import styles from "./Directory.module.scss"; +import { PersonaCard } from "./PersonaCard/PersonaCard"; +import { spservices } from "../../../SPServices/spservices"; +import { IDirectoryState } from "./IDirectoryState"; +import * as strings from "DirectoryWebPartStrings"; +import { + Spinner, SpinnerSize, MessageBar, MessageBarType, SearchBox, Icon, Label, + Pivot, PivotItem, PivotLinkFormat, PivotLinkSize, Dropdown, IDropdownOption +} from "office-ui-fabric-react"; +import { Stack, IStackStyles, IStackTokens } from 'office-ui-fabric-react/lib/Stack'; +import { debounce } from "throttle-debounce"; +import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle"; +import { ISPServices } from "../../../SPServices/ISPServices"; +import { Environment, EnvironmentType } from "@microsoft/sp-core-library"; +import { spMockServices } from "../../../SPServices/spMockServices"; +import { IDirectoryProps } from './IDirectoryProps'; +import Paging from './Pagination/Paging'; + +const slice: any = require('lodash/slice'); +const wrapStackTokens: IStackTokens = { childrenGap: 30 }; + +const DirectoryHook: React.FC = (props) => { + let _services: ISPServices = null; + if (Environment.type === EnvironmentType.Local) { + _services = new spMockServices(); + } else { + _services = new spservices(props.context); + } + const [az, setaz] = useState([]); + const [alphaKey, setalphaKey] = useState('A'); + const [state, setstate] = useState({ + users: [], + isLoading: true, + errorMessage: "", + hasError: false, + indexSelectedKey: "A", + searchString: "LastName", + searchText: "" + }); + const orderOptions: IDropdownOption[] = [ + { key: "FirstName", text: "First Name" }, + { key: "LastName", text: "Last Name" }, + { key: "Department", text: "Department" }, + { key: "Location", text: "Location" }, + { key: "JobTitle", text: "Job Title" } + ]; + const color = props.context.microsoftTeams ? "white" : ""; + // Paging + const [pagedItems, setPagedItems] = useState([]); + const [pageSize, setPageSize] = useState(props.pageSize ? props.pageSize : 10); + const [currentPage, setCurrentPage] = useState(1); + + const _onPageUpdate = async (pageno?: number) => { + var currentPge = (pageno) ? pageno : currentPage; + var startItem = ((currentPge - 1) * pageSize); + var endItem = currentPge * pageSize; + let filItems = slice(state.users, startItem, endItem); + setCurrentPage(currentPge); + setPagedItems(filItems); + }; + + const diretoryGrid = + pagedItems && pagedItems.length > 0 + ? pagedItems.map((user: any) => { + return ( + + ); + }) + : []; + const _loadAlphabets = () => { + let alphabets: string[] = []; + for (let i = 65; i < 91; i++) { + alphabets.push( + String.fromCharCode(i) + ); + } + setaz(alphabets); + }; + + const _alphabetChange = async (item?: PivotItem, ev?: React.MouseEvent) => { + setstate({ ...state, searchText: "", indexSelectedKey: item.props.itemKey, isLoading: true }); + setalphaKey(item.props.itemKey); + setCurrentPage(1); + }; + const _searchByAlphabets = async (initialSearch: boolean) => { + setstate({ ...state, isLoading: true, searchText: '' }); + let users = null; + if (initialSearch) { + if (props.searchFirstName) + users = await _services.searchUsersNew('', `FirstName:a*`, false); + else users = await _services.searchUsersNew('a', '', true); + } else { + if (props.searchFirstName) + users = await _services.searchUsersNew('', `FirstName:${alphaKey}*`, false); + else users = await _services.searchUsersNew(`${alphaKey}`, '', true); + } + setstate({ + ...state, + searchText: '', + indexSelectedKey: initialSearch ? 'A' : state.indexSelectedKey, + users: + users && users.PrimarySearchResults + ? users.PrimarySearchResults + : null, + isLoading: false, + errorMessage: "", + hasError: false + }); + }; + + let _searchUsers = async (searchText: string) => { + try { + console.log("Search Users: ", searchText); + setstate({ ...state, searchText: searchText, isLoading: true }); + if (searchText.length > 0) { + let searchProps: string[] = props.searchProps && props.searchProps.length > 0 ? + props.searchProps.split(',') : ['FirstName', 'LastName', 'WorkEmail', 'Department']; + let qryText: string = ''; + searchProps.map((srchprop, index) => { + if (index == searchProps.length - 1) + qryText += `${srchprop}:${searchText}*`; + else qryText += `${srchprop}:${searchText}* OR `; + }); + const users = await _services.searchUsersNew('', qryText, false); + setstate({ + ...state, + searchText: searchText, + //indexSelectedKey: (searchText.length > 0) ? searchText.substring(0, 1).toLocaleUpperCase() : 'A', + indexSelectedKey: '0', + users: + users && users.PrimarySearchResults + ? users.PrimarySearchResults + : null, + isLoading: false, + errorMessage: "", + hasError: false + }); + setalphaKey('0'); + } else { + setstate({ ...state, searchText: '' }); + _searchByAlphabets(true); + } + } catch (err) { + setstate({ ...state, errorMessage: err.message, hasError: true }); + } + }; + + const _searchBoxChanged = (newvalue: string): void => { + setCurrentPage(1); + _searchUsers(newvalue); + }; + _searchUsers = debounce(500, _searchUsers); + + const _sortPeople = async (sortField: string) => { + let _users = state.users; + _users = _users.sort((a: any, b: any) => { + switch (sortField) { + // Sorte by FirstName + case "FirstName": + const aFirstName = a.FirstName ? a.FirstName : ""; + const bFirstName = b.FirstName ? b.FirstName : ""; + if (aFirstName.toUpperCase() < bFirstName.toUpperCase()) { + return -1; + } + if (aFirstName.toUpperCase() > bFirstName.toUpperCase()) { + return 1; + } + return 0; + break; + // Sort by LastName + case "LastName": + const aLastName = a.LastName ? a.LastName : ""; + const bLastName = b.LastName ? b.LastName : ""; + if (aLastName.toUpperCase() < bLastName.toUpperCase()) { + return -1; + } + if (aLastName.toUpperCase() > bLastName.toUpperCase()) { + return 1; + } + return 0; + break; + // Sort by Location + case "Location": + const aBaseOfficeLocation = a.BaseOfficeLocation + ? a.BaseOfficeLocation + : ""; + const bBaseOfficeLocation = b.BaseOfficeLocation + ? b.BaseOfficeLocation + : ""; + if ( + aBaseOfficeLocation.toUpperCase() < + bBaseOfficeLocation.toUpperCase() + ) { + return -1; + } + if ( + aBaseOfficeLocation.toUpperCase() > + bBaseOfficeLocation.toUpperCase() + ) { + return 1; + } + return 0; + break; + // Sort by JobTitle + case "JobTitle": + const aJobTitle = a.JobTitle ? a.JobTitle : ""; + const bJobTitle = b.JobTitle ? b.JobTitle : ""; + if (aJobTitle.toUpperCase() < bJobTitle.toUpperCase()) { + return -1; + } + if (aJobTitle.toUpperCase() > bJobTitle.toUpperCase()) { + return 1; + } + return 0; + break; + // Sort by Department + case "Department": + const aDepartment = a.Department ? a.Department : ""; + const bDepartment = b.Department ? b.Department : ""; + if (aDepartment.toUpperCase() < bDepartment.toUpperCase()) { + return -1; + } + if (aDepartment.toUpperCase() > bDepartment.toUpperCase()) { + return 1; + } + return 0; + break; + default: + break; + } + }); + setstate({ ...state, users: _users, searchString: sortField }); + }; + + useEffect(() => { + setPageSize(props.pageSize); + if (state.users) _onPageUpdate(); + }, [state.users, props.pageSize]); + + useEffect(() => { + console.log("Alpha change"); + if (alphaKey.length > 0 && alphaKey != "0") _searchByAlphabets(false); + }, [alphaKey]); + + useEffect(() => { + console.log("yes"); + _loadAlphabets(); + _searchByAlphabets(true); + }, [props]); + + return ( +
+ +
+ +
+ + {az.map((index: string) => { + return ( + + ); + })} + +
+
+ {state.isLoading ? ( +
+ +
+ ) : ( + <> + {state.hasError ? ( +
+ + {state.errorMessage} + +
+ ) : ( + <> + {!pagedItems || pagedItems.length == 0 ? ( +
+ + +
+ ) : ( + <> +
+ +
+
+ + { + _sortPeople(value.key.toString()); + }} + styles={{ dropdown: { width: 200 } }} + /> + +
+ +
{diretoryGrid}
+
+
+ +
+ + )} + + )} + + )} +
+ ); +}; + +export default DirectoryHook; \ No newline at end of file diff --git a/samples/react-directory/src/webparts/directory/components/Pagination/Paging.module.scss b/samples/react-directory/src/webparts/directory/components/Pagination/Paging.module.scss new file mode 100644 index 000000000..463cf8706 --- /dev/null +++ b/samples/react-directory/src/webparts/directory/components/Pagination/Paging.module.scss @@ -0,0 +1,33 @@ +.paginationContainer { + text-align: right; + font-size: 14px; + font-family: Calibri !important; + .searchWp__paginationContainer__pagination { + // display: inline-block; + text-align: center; + ul { + display: inline-block; + padding-left: 0; + border-radius: 4px; + li { + display: inline; + a { + float: left; + padding: 5px 10px; + text-decoration: none; + border-radius: 15px; + i { + font-size: 10px; + } + } + a:visited { + color: inherit; + } + a.active { + background-color: "[theme:themePrimary]" !important; + color: "[theme:white]" !important; + } + } + } + } + } \ No newline at end of file diff --git a/samples/react-directory/src/webparts/directory/components/Pagination/Paging.tsx b/samples/react-directory/src/webparts/directory/components/Pagination/Paging.tsx new file mode 100644 index 000000000..3a04c0745 --- /dev/null +++ b/samples/react-directory/src/webparts/directory/components/Pagination/Paging.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import Pagination from 'react-js-pagination'; +import styles from './Paging.module.scss'; + +export type PageUpdateCallback = (pageNumber: number) => void; + +export interface IPagingProps { + totalItems: number; + itemsCountPerPage: number; + onPageUpdate: PageUpdateCallback; + currentPage: number; +} + +export interface IPagingState { + currentPage: number; +} + +const Paging: React.FC = (props) => { + const [currentPage, setcurrentPage] = useState(props.currentPage); + + const _pageChange = (pageNumber: number): void => { + setcurrentPage(pageNumber); + props.onPageUpdate(pageNumber); + }; + + useEffect(() => { + setcurrentPage(props.currentPage); + }, [props.currentPage]); + + return ( +
+
+
+
+ ); +}; + +export default Paging; \ No newline at end of file