Merge pull request #1507 from sudharsank/react-directory

This commit is contained in:
Hugo Bernier 2020-09-24 01:09:15 -04:00 committed by GitHub
commit c2ad4e94f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1622 additions and 4587 deletions

View File

@ -28,10 +28,13 @@
![directory](/samples/react-directory/assets/react-directory5.jpg)
![directory](./assets/react-directory-withPaging.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.11-green.svg)
![SPFx 1.11](https://img.shields.io/badge/version-1.11-green.svg)
## Applies to
@ -46,17 +49,21 @@ Property |Type|Required| comments
--------------------|----|--------|----------
Title | Text| no|WebPart Title
searchFirstName | boolean|no| Lastname or Firstname search query
Properties to search | text | no | By default **FirstName,LastName,WorkEmail,Department** are used for search. You can add custom properties separated by comma.
Results per page | number | Number of people result to be displayed per page. Max of **20** is allowed, default of **10** is set.
## Solution
The web part use PnPjs library, Office-ui-fabric-react components
Solution|Author(s)
--------|---------
Directory Web Part|João Mendes
Directory Web Part| Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at))
Directory Web Part| Sudharsan K ([@sudharsank](https://twitter.com/sudharsank))
## Version history
@ -64,6 +71,7 @@ Version|Date|Comments
-------|----|--------
1.0.0|July 29, 2019|Initial release
1.0.1|July 19, 2020|Bugfix and mock-service for workbench (```LivePersonaCard``` not supported in workbench)
2.0.0.0|Sep 18 2020|React hooks, paging, dynamic search props, result alignment using office ui fabric stack.
## Disclaimer

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@ -10,7 +10,7 @@
},
"name": "Search Directory",
"id": "5b62bc16-3a71-461d-be2f-16bfcb011e8a",
"version": "1.0.1.0",
"version": "2.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false

File diff suppressed because it is too large Load Diff

View File

@ -26,9 +26,12 @@
"@types/jquery": "^3.3.30",
"@uifabric/fluent-theme": "^0.16.9",
"jquery": "^3.4.1",
"lodash": "^4.17.20",
"office-ui-fabric-react": "6.214.0",
"react": "16.7.0",
"react-dom": "16.7.0"
"react-dom": "16.7.0",
"react-js-pagination": "^3.0.3",
"throttle-debounce": "^2.3.0"
},
"resolutions": {
"@types/react": "16.8.8"

View File

@ -3,5 +3,6 @@ import { PeoplePickerEntity } from '@pnp/sp';
export interface ISPServices {
searchUsers(searchString: string, searchFirstName: boolean);
searchUsersNew(searchString: string, srchQry: string, isInitialSearch: boolean, pageNumber?: number);
}

View File

@ -109,4 +109,35 @@ export class spMockServices implements ISPServices {
}
public async searchUsersNew(searchString: string, srchQry: string, isInitialSearch: boolean, pageNumber?: number) {
let filtervalue = searchString.trim().toLowerCase();
if (searchString.length > 0 && filtervalue.lastIndexOf("*") == searchString.length - 1) {
// remove last '*'
filtervalue = filtervalue.substring(0, searchString.length - 1);
}
if (!filtervalue || filtervalue.length === 0) {
throw new Error("No valid Input.");
}
const searchresult = !!isInitialSearch ?
this.sampleData.filter(p => p.FirstName.toLowerCase().indexOf(filtervalue) === 0) :
this.sampleData.filter(p => p.LastName.toLowerCase().indexOf(filtervalue) === 0);
const timeout = Math.floor(Math.random() * (1000)) + 1;
const resultdata = {
ElapsedTime: timeout,
RowCount: searchresult.length,
TotalRows: searchresult.length,
PrimarySearchResults: searchresult
};
return new Promise((resolve) => {
setTimeout(() => {
resolve(resultdata);
}, timeout);
});
}
}

View File

@ -7,38 +7,83 @@ import { ISPServices } from "./ISPServices";
export class spservices implements ISPServices {
constructor(private context: WebPartContext) {
sp.setup({
spfxContext: this.context
});
}
public async searchUsers(searchString: string, searchFirstName: boolean): Promise<SearchResults> {
const _search = !searchFirstName ? `LastName:${searchString}*` : `FirstName:${searchString}*`;
const searchProperties: string[] = ["FirstName", "LastName", "PreferredName", "WorkEmail", "OfficeNumber", "PictureURL", "WorkPhone", "MobilePhone", "JobTitle", "Department", "Skills", "PastProjects", "BaseOfficeLocation", "SPS-UserType", "GroupId"];
try {
if (!searchString) return undefined;
let users = await sp.searchWithCaching(<SearchQuery>{
Querytext: _search,
RowLimit: 500,
EnableInterleaving: true,
SelectProperties: searchProperties,
SourceId: 'b09a7990-05ea-4af9-81ef-edfab16c4e31',
SortList: [{ "Property": "LastName", "Direction": SortDirection.Ascending }],
});
return users;
} catch (error) {
Promise.reject(error);
constructor(private context: WebPartContext) {
sp.setup({
spfxContext: this.context
});
}
public async searchUsers(searchString: string, searchFirstName: boolean): Promise<SearchResults> {
const _search = !searchFirstName ? `LastName:${searchString}*` : `FirstName:${searchString}*`;
const searchProperties: string[] = ["FirstName", "LastName", "PreferredName", "WorkEmail", "OfficeNumber", "PictureURL", "WorkPhone", "MobilePhone", "JobTitle", "Department", "Skills", "PastProjects", "BaseOfficeLocation", "SPS-UserType", "GroupId"];
try {
if (!searchString) return undefined;
let users = await sp.searchWithCaching(<SearchQuery>{
Querytext: _search,
RowLimit: 500,
EnableInterleaving: true,
SelectProperties: searchProperties,
SourceId: 'b09a7990-05ea-4af9-81ef-edfab16c4e31',
SortList: [{ "Property": "LastName", "Direction": SortDirection.Ascending }],
});
return users;
} catch (error) {
Promise.reject(error);
}
}
public async _getImageBase64 (pictureUrl: string): Promise<string> {
return new Promise((resolve, reject) => {
let image = new Image();
image.addEventListener("load", () => {
let tempCanvas = document.createElement("canvas");
tempCanvas.width = image.width,
tempCanvas.height = image.height,
tempCanvas.getContext("2d").drawImage(image, 0, 0);
let base64Str;
try {
base64Str = tempCanvas.toDataURL("image/png");
} catch (e) {
return "";
}
resolve(base64Str);
});
image.src = pictureUrl;
});
}
public async searchUsersNew(searchString: string, srchQry: string, isInitialSearch: boolean, pageNumber?: number): Promise<SearchResults> {
let qrytext: string = '';
if (isInitialSearch) qrytext = `FirstName:${searchString}* OR LastName:${searchString}*`;
else {
if (srchQry) qrytext = srchQry;
else {
if (searchString) qrytext = searchString;
}
if (qrytext.length <= 0) qrytext = `*`;
}
const searchProperties: string[] = ["FirstName", "LastName", "PreferredName", "WorkEmail", "OfficeNumber", "PictureURL", "WorkPhone", "MobilePhone", "JobTitle", "Department", "Skills", "PastProjects", "BaseOfficeLocation", "SPS-UserType", "GroupId"];
try {
let users = await sp.search(<SearchQuery>{
Querytext: qrytext,
RowLimit: 500,
EnableInterleaving: true,
SelectProperties: searchProperties,
SourceId: 'b09a7990-05ea-4af9-81ef-edfab16c4e31',
SortList: [{ "Property": "LastName", "Direction": SortDirection.Ascending }],
});
if (users && users.PrimarySearchResults.length > 0) {
for (let index = 0; index < users.PrimarySearchResults.length; index++) {
let user: any = users.PrimarySearchResults[index];
if (user.PictureURL) {
user = { ...user, PictureURL: `/_layouts/15/userphoto.aspx?size=M&accountname=${user.WorkEmail}` };
users.PrimarySearchResults[index] = user;
}
}
}
return users;
} catch (error) {
Promise.reject(error);
}
}
}
}

View File

@ -22,7 +22,9 @@
"officeFabricIconFontName": "ProfileSearch",
"properties": {
"title": "Directory",
"searchFirstName": 0
"searchFirstName": 0,
"searchProps": "FirstName,LastName,PreferredName,WorkEmail,Department",
"pageSize": 10
}
}]
}

View File

@ -2,74 +2,108 @@ import * as React from "react";
import * as ReactDom from "react-dom";
import { Version } from "@microsoft/sp-core-library";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { DisplayMode } from "@microsoft/sp-core-library";
import {
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneToggle,
IPropertyPaneToggleProps
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneToggle,
IPropertyPaneToggleProps,
PropertyPaneSlider
} from "@microsoft/sp-property-pane";
import * as strings from "DirectoryWebPartStrings";
import Directory from "./components/Directory";
import DirectoryHook from "./components/DirectoryHook";
import { IDirectoryProps } from "./components/IDirectoryProps";
export interface IDirectoryWebPartProps {
title: string;
searchFirstName: boolean;
title: string;
searchFirstName: boolean;
searchProps: string;
pageSize: number;
}
export default class DirectoryWebPart extends BaseClientSideWebPart<
IDirectoryWebPartProps
> {
public render(): void {
const element: React.ReactElement<IDirectoryProps> = React.createElement(
Directory,
{
title: this.properties.title,
context: this.context,
searchFirstName: this.properties.searchFirstName,
displayMode: this.displayMode,
updateProperty: (value: string) => {
this.properties.title = value;
}
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse("1.0");
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
IDirectoryWebPartProps
> {
public render(): void {
const element: React.ReactElement<IDirectoryProps> = React.createElement(
// Directory,
// {
// title: this.properties.title,
// context: this.context,
// searchFirstName: this.properties.searchFirstName,
// displayMode: this.displayMode,
// updateProperty: (value: string) => {
// this.properties.title = value;
// }
// },
DirectoryHook,
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField("title", {
label: strings.TitleFieldLabel
}),
PropertyPaneToggle("searchFirstName", {
checked: false,
label: "Search on First Name ?"
})
]
title: this.properties.title,
context: this.context,
searchFirstName: this.properties.searchFirstName,
displayMode: this.displayMode,
updateProperty: (value: string) => {
this.properties.title = value;
},
searchProps: this.properties.searchProps,
pageSize: this.properties.pageSize
}
]
}
]
};
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse("1.0");
}
protected get disableReactivePropertyChanges(): boolean {
return true;
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField("title", {
label: strings.TitleFieldLabel
}),
PropertyPaneToggle("searchFirstName", {
checked: false,
label: "Search on First Name ?"
}),
PropertyPaneTextField('searchProps', {
label: strings.SearchPropsLabel,
description: strings.SearchPropsDesc,
value: this.properties.searchProps,
multiline: false,
resizable: false
}),
PropertyPaneSlider('pageSize', {
label: 'Results per page',
showValue: true,
max: 20,
min: 2,
step: 2,
value: this.properties.pageSize
})
]
}
]
}
]
};
}
}

View File

@ -2,7 +2,6 @@
.directory {
.dropDownSortBy {
margin-top: 35px;
margin-bottom: 15px;
}
@ -98,4 +97,18 @@
display: inline-block;
}
}
.alphabets {
padding-left: 10px;
padding-right: 10px;
white-space: normal;
text-align: center;
}
.searchTextBox {
min-width: 180px;
max-width: 300px;
margin-left: auto;
margin-right: auto;
margin-bottom: 25px;
}
}

View File

@ -0,0 +1,355 @@
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<IDirectoryProps> = (props) => {
let _services: ISPServices = null;
if (Environment.type === EnvironmentType.Local) {
_services = new spMockServices();
} else {
_services = new spservices(props.context);
}
const [az, setaz] = useState<string[]>([]);
const [alphaKey, setalphaKey] = useState<string>('A');
const [state, setstate] = useState<IDirectoryState>({
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<any[]>([]);
const [pageSize, setPageSize] = useState<number>(props.pageSize ? props.pageSize : 10);
const [currentPage, setCurrentPage] = useState<number>(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 (
<PersonaCard
context={props.context}
profileProperties={{
DisplayName: user.PreferredName,
Title: user.JobTitle,
PictureUrl: user.PictureURL,
Email: user.WorkEmail,
Department: user.Department,
WorkPhone: user.WorkPhone,
Location: user.OfficeNumber
? user.OfficeNumber
: user.BaseOfficeLocation
}}
/>
);
})
: [];
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<HTMLElement>) => {
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 {
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 = '';
let finalSearchText: string = searchText ? searchText.replace(/ /g, '+') : searchText;
searchProps.map((srchprop, index) => {
if (index == searchProps.length - 1)
qryText += `${srchprop}:${finalSearchText}*`;
else qryText += `${srchprop}:${finalSearchText}* OR `;
});
const users = await _services.searchUsersNew('', qryText, false);
setstate({
...state,
searchText: searchText,
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 (
<div className={styles.directory}>
<WebPartTitle displayMode={props.displayMode} title={props.title}
updateProperty={props.updateProperty} />
<div className={styles.searchBox}>
<SearchBox placeholder={strings.SearchPlaceHolder} className={styles.searchTextBox}
onSearch={_searchUsers}
value={state.searchText}
onChange={_searchBoxChanged} />
<div>
<Pivot className={styles.alphabets} linkFormat={PivotLinkFormat.tabs}
selectedKey={state.indexSelectedKey} onLinkClick={_alphabetChange}
linkSize={PivotLinkSize.normal} >
{az.map((index: string) => {
return (
<PivotItem headerText={index} itemKey={index} key={index} />
);
})}
</Pivot>
</div>
</div>
{state.isLoading ? (
<div style={{ marginTop: '10px' }}>
<Spinner size={SpinnerSize.large} label={strings.LoadingText} />
</div>
) : (
<>
{state.hasError ? (
<div style={{ marginTop: '10px' }}>
<MessageBar messageBarType={MessageBarType.error}>
{state.errorMessage}
</MessageBar>
</div>
) : (
<>
{!pagedItems || pagedItems.length == 0 ? (
<div className={styles.noUsers}>
<Icon
iconName={"ProfileSearch"}
style={{ fontSize: "54px", color: color }}
/>
<Label>
<span style={{ marginLeft: 5, fontSize: "26px", color: color }}>
{strings.DirectoryMessage}
</span>
</Label>
</div>
) : (
<>
<div style={{ width: '100%', display: 'inline-block' }}>
<Paging
totalItems={state.users.length}
itemsCountPerPage={pageSize}
onPageUpdate={_onPageUpdate}
currentPage={currentPage} />
</div>
<div className={styles.dropDownSortBy}>
<Stack horizontal horizontalAlign="center" wrap tokens={wrapStackTokens}>
<Dropdown
placeholder={strings.DropDownPlaceHolderMessage}
label={strings.DropDownPlaceLabelMessage}
options={orderOptions}
selectedKey={state.searchString}
onChange={(ev: any, value: IDropdownOption) => {
_sortPeople(value.key.toString());
}}
styles={{ dropdown: { width: 200 } }}
/>
</Stack>
</div>
<Stack horizontal horizontalAlign="center" wrap tokens={wrapStackTokens}>
<div>{diretoryGrid}</div>
</Stack>
<div style={{ width: '100%', display: 'inline-block' }}>
<Paging
totalItems={state.users.length}
itemsCountPerPage={pageSize}
onPageUpdate={_onPageUpdate}
currentPage={currentPage} />
</div>
</>
)}
</>
)}
</>
)}
</div>
);
};
export default DirectoryHook;

View File

@ -6,4 +6,6 @@ export interface IDirectoryProps {
context: WebPartContext;
searchFirstName: boolean;
updateProperty: (value: string) => void;
searchProps?: string;
pageSize?: number;
}

View File

@ -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;
}
}
}
}
}

View File

@ -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<IPagingProps> = (props) => {
const [currentPage, setcurrentPage] = useState<number>(props.currentPage);
const _pageChange = (pageNumber: number): void => {
setcurrentPage(pageNumber);
props.onPageUpdate(pageNumber);
};
useEffect(() => {
setcurrentPage(props.currentPage);
}, [props.currentPage]);
return (
<div className={styles.paginationContainer}>
<div className={styles.searchWp__paginationContainer__pagination}>
<Pagination
activePage={currentPage}
firstPageText={<i className='ms-Icon ms-Icon--DoubleChevronLeft' aria-hidden='true'></i>}
lastPageText={<i className='ms-Icon ms-Icon--DoubleChevronRight' aria-hidden='true'></i>}
prevPageText={<i className='ms-Icon ms-Icon--ChevronLeft' aria-hidden='true'></i>}
nextPageText={<i className='ms-Icon ms-Icon--ChevronRight' aria-hidden='true'></i>}
activeLinkClass={styles.active}
itemsCountPerPage={props.itemsCountPerPage}
totalItemsCount={props.totalItems}
pageRangeDisplayed={5}
onChange={_pageChange}
/>
</div>
</div>
);
};
export default Paging;

View File

@ -4,8 +4,8 @@
display: inline-block;
margin-top: 15px;
margin-right: 15px;
min-width: 300;
min-width: 300px;
max-width: 300px;
vertical-align: top;
}
.documentCard {
@ -25,3 +25,13 @@
margin-top: 10;
width: 100%;
}
.textOverflow {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}

View File

@ -39,7 +39,6 @@ export class PersonaCard extends React.Component<
LIVE_PERSONA_COMPONENT_ID
);
const livePersonaCard: any = sharedLibrary.LivePersonaCard;
console.log(livePersonaCard);
this.setState({ livePersonaCard: livePersonaCard });
}
}
@ -115,7 +114,7 @@ export class PersonaCard extends React.Component<
''
)}
{this.props.profileProperties.Location ? (
<div>
<div className={styles.textOverflow}>
<Icon iconName="Poi" style={{ fontSize: '12px' }} />
<span style={{ marginLeft: 5, fontSize: '12px' }}>
{' '}

View File

@ -6,6 +6,10 @@ define([], function() {
"PropertyPaneDescription": "Search People from Organization Directory",
"BasicGroupName": "Properties",
"TitleFieldLabel": "Web Part Title",
"DirectoryMessage": "No users found in directory"
"DirectoryMessage": "No users found in directory",
"LoadingText": "Searching for user. Please wait...",
"SearchPropsLabel": "Properties to search",
"SearchPropsDesc": "Enter the properties separated by comma to be used for search",
"PagingLabel": "Results per page"
}
});

View File

@ -6,7 +6,10 @@ declare interface IDirectoryWebPartStrings {
BasicGroupName: string;
TitleFieldLabel: string;
DirectoryMessage: string;
LoadingText: string;
SearchPropsLabel: string;
SearchPropsDesc: string;
PagingLabel: string;
}
declare module 'DirectoryWebPartStrings' {