import { Web } from '@pnp/sp' ; import { ProjectListModel } from 'models' ; import MSGraph from 'msgraph-helper' ; import { Pivot , PivotItem , ShimmeredDetailsList } from 'office-ui-fabric-react' ; import { IButtonProps } from 'office-ui-fabric-react/lib/Button' ; import { IColumn , SelectionMode } from 'office-ui-fabric-react/lib/DetailsList' ; import { MessageBar , MessageBarType } from 'office-ui-fabric-react/lib/MessageBar' ; import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox' ; import { Toggle } from 'office-ui-fabric-react/lib/Toggle' ; import * as strings from 'PortfolioWebPartsStrings' ; import { ProjectInformationModal } from 'pp365-projectwebparts/lib/components/ProjectInformation' ; import { getObjectValue , sortAlphabetically } from 'pp365-shared/lib/helpers' ; import React , { FunctionComponent , useEffect , useState } from 'react' ; import { find , isEmpty } from 'underscore' ; import { ProjectCard } from './ProjectCard' ; import styles from './ProjectList.module.scss' ; import { PROJECTLIST_COLUMNS } from './ProjectListColumns' ; import { IProjectListProps , IProjectListState } from './types' ; export const ProjectList = ( props ) => { const [ state , setState ] = useState < IProjectListState > ( { loading : true , searchTerm : '' , showAsTiles : props . showAsTiles , selectedView : 'my_projects' , projects : [ ] , sort : { fieldName : props . sortBy , isSortedDescending : true } } ) ; param projects function renderProjects ( projects ) { if ( state . showAsTiles ) { return projects . map ( ( project , idx ) => ( < ProjectCard key = { idx } project = { project } shouldTruncateTitle = { true } showProjectLogo = { props . showProjectLogo } showProjectOwner = { props . showProjectOwner } showProjectManager = { props . showProjectManager } actions = { getCardActions ( project ) } / > ) ) ; } else { const columns = props . columns . map ( ( col ) => { col . isSorted = col . key === state . sort ?. fieldName ; if ( col . isSorted ) { col . isSortedDescending = state . sort ?. isSortedDescending ; } return col ; } ) ; return ( < ShimmeredDetailsList enableShimmer = { state . loading } items = { projects } columns = { columns } onRenderItemColumn = { onRenderItemColumn } onColumnHeaderClick = { onListSort } selectionMode = { SelectionMode . none } / > ) ; } } param project param _index param column function onRenderItemColumn ( project , _index , column ) { const colValue = getObjectValue ( project , column . fieldName , null ) ; if ( column . fieldName === 'title' ) { if ( project . userIsMember ) return < a href = { project . url } > { colValue } < /a>; return < > { colValue } < />; } return colValue ; } param _evt param column function onListSort ( _evt , column ) { let isSortedDescending = column . isSortedDescending ; if ( column . isSorted ) { isSortedDescending = ! isSortedDescending ; } setState ( { ... state , sort : { fieldName : column . fieldName , isSortedDescending } } ) ; } /**
     * Render <ProjectInformationModal />
     */ function renderProjectInformation ( ) { if ( state . showProjectInfo ) { return ( < ProjectInformationModal modalProps = { { isOpen : true , onDismiss : ( ) => setState ( { ... state , showProjectInfo : null } ) } } title = { state . showProjectInfo . title } webUrl = { props . pageContext . site . absoluteUrl } hubSite = { { web : new Web ( props . pageContext . site . absoluteUrl ) , url : props . pageContext . site . absoluteUrl } } siteId = { state . showProjectInfo . siteId } hideActions = { true } page = 'Portfolio' / > ) ; } return null ; } param project function getCardActions ( project ) { return [ { id : 'ON_SELECT_PROJECT' , iconProps : { iconName : 'OpenInNewWindow' } , onClick : ( event ) => onExecuteCardAction ( event , project ) } ] ; } param event param project function onExecuteCardAction ( event , project ) { event . preventDefault ( ) ; event . stopPropagation ( ) ; switch ( event . currentTarget . id ) { case 'ON_SELECT_PROJECT' : { setState ( { ... state , showProjectInfo : project } ) ; } break ; } } param projects function filterProjets ( projects ) { return projects . filter ( ( project ) => { if ( state . selectedView === 'my_projects' ) return project . userIsMember ; return true ; } ) . filter ( ( p ) => { const matches = Object . keys ( p ) . filter ( ( key ) => { const value = p [ key ] ; return ( value && typeof value === 'string' && value . toLowerCase ( ) . indexOf ( state . searchTerm ) !== - 1 ) ; } ) . length ; return matches > 0 ; } ) . sort ( ( a , b ) => sortAlphabetically < ProjectListModel > ( a , b , state ?. sort ?. isSortedDescending , state ?. sort ?. fieldName ) ) ; } param searchTerm function onSearch ( searchTerm ) { setState ( { ... state , searchTerm : searchTerm . toLowerCase ( ) } ) ; } param projects param batchSize async function getProjectLogos ( projects , batchSize = 20 ) { const batchReq = projects . map ( ( p ) => ( { id : p . groupId , method : 'GET' , url : `groups/${ p . groupId }/photo/$value` } ) ) ; while ( ! isEmpty ( batchReq ) ) { const { responses } = await MSGraph . Batch ( batchReq . splice ( 0 , batchSize ) ) ; const projects_ = projects . map ( ( p ) => { const response = find ( responses , ( r ) => r . id === p . groupId && r . status === 200 ) ; if ( response ) { p . logo = `data:image/png;base64, ${ response . body }` ; } return p ; } ) ; setState ( ( prevState ) => ( { ... prevState , projects : projects_ } ) ) ; } } /**
     * Get searchbox placeholder text based on `state.selectedView`
     */ function getSearchBoxPlaceholder ( ) { switch ( state . selectedView ) { case 'my_projects' : return strings . MyProjectsSearchBoxPlaceholderText ; case 'all_projects' : return strings . AllProjectsSearchBoxPlaceholderText ; } } useEffect ( ( ) => { Promise . all ( [ props . dataAdapter . fetchEncrichedProjects ( ) , props . dataAdapter . isUserInGroup ( strings . PortfolioManagerGroupName ) ] ) . then ( ( [ projects , isUserInPortfolioManagerGroup ] ) => { setState ( { ... state , projects , loading : false , isUserInPortfolioManagerGroup } ) ; if ( props . showProjectLogo ) { getProjectLogos ( projects , 20 ) ; } } ) ; } , [ ] ) ; if ( state . error ) { return ( < div className = { styles . root } > < MessageBar messageBarType = { MessageBarType . error } > { strings . ErrorText } < /MessageBar> < /div>); } const projects = filterProjets ( state . projects ) ; return ( < div className = { styles . root } > < div className = { styles . container } > < div className = { styles . searchBox } hidden = { ! props . showSearchBox } > < SearchBox placeholder = { getSearchBoxPlaceholder ( ) } onChanged = { onSearch } / > < /div> { state . isUserInPortfolioManagerGroup && ( < div className = { styles . projectDisplaySelect } > < Pivot onLinkClick = { ( { props } ) => setState ( { ... state , selectedView : props . itemKey } ) } selectedKey = { state . selectedView } > < PivotItem headerText = { strings . MyProjectsLabel } itemKey = 'my_projects' / > < PivotItem headerText = { strings . AllProjectsLabel } itemKey = 'all_projects' / > < /Pivot> < /div>)} < div className = { styles . viewToggle } hidden = { ! props . showViewSelector } > < Toggle offText = { strings . ShowAsListText } onText = { strings . ShowAsTilesText } defaultChecked = { state . showAsTiles } inlineLabel = { true } onChanged = { ( showAsTiles ) => setState ( { ... state , showAsTiles } ) } /> < /div> < div className = { styles . emptyMessage } hidden = { projects . length > 0 } > < MessageBar > { strings . NoSearchResults } < /MessageBar> < /div> < div className = { styles . projects } hidden = { projects . length === 0 } > { renderProjects ( projects ) } < /div> < /div> { renderProjectInformation ( ) } < /div>); } ; ProjectList . defaultProps = { columns : PROJECTLIST_COLUMNS , sortBy : 'Title' } ; export * from './types' ;