1+ import * as React from 'react' ;
2+ import { useState , useEffect } from 'react' ;
3+ import { IInputs } from "../generated/ManifestTypes" ;
4+ import { Fabric } from 'office-ui-fabric-react/lib/Fabric' ;
5+ import { TextField } from 'office-ui-fabric-react/lib/TextField' ;
6+ import { SelectionMode , IColumn } from 'office-ui-fabric-react/lib/DetailsList' ;
7+ import { ShimmeredDetailsList , } from 'office-ui-fabric-react/lib/ShimmeredDetailsList' ;
8+ import { Icon } from '@fluentui/react/lib/Icon' ;
9+
10+ export interface IResponseBag {
11+ boundLookupId : string | undefined ,
12+ approvalLookupSchemaName : string | undefined ,
13+ requests : ComponentFramework . WebApi . Entity [ ] ,
14+ responses : ComponentFramework . WebApi . Entity [ ]
15+ }
16+
17+ export interface ITableRow {
18+ key : string ,
19+ name : string | undefined ,
20+ status : JSX . Element | undefined ,
21+ comments : string | undefined ,
22+ }
23+
24+ export const Approvals = ( _context : ComponentFramework . Context < IInputs > ) => {
25+
26+ // ### SLICES
27+ const [ approvalState , setApprovalState ] = useState ( '' ) ;
28+ const [ isLoadingState , setIsLoadingState ] = useState ( true ) ;
29+ const [ tableDataState , setTableDataState ] = useState ( [ ] as ITableRow [ ] ) ;
30+ const [ tableDataMasterState , setTableDataMasterState ] = useState ( [ ] as ITableRow [ ] ) ;
31+ const [ columnState , setColumnState ] = useState ( [
32+ { //name
33+ key : 'col1' ,
34+ name : 'Name' ,
35+ fieldName : 'name' ,
36+ minWidth : 210 ,
37+ maxWidth : 350 ,
38+ isRowHeader : true ,
39+ isResizable : true ,
40+ isSorted : false ,
41+ isSortedDescending : false ,
42+ sortAscendingAriaLabel : 'Sorted A to Z' ,
43+ sortDescendingAriaLabel : 'Sorted Z to A' ,
44+ data : 'string' ,
45+ isPadded : true ,
46+ } ,
47+ { //status
48+ key : 'col2' ,
49+ name : 'Status' ,
50+ fieldName : 'status' ,
51+ minWidth : 100 ,
52+ maxWidth : 150 ,
53+ isRowHeader : true ,
54+ isResizable : false ,
55+ data : 'string' ,
56+ isPadded : true ,
57+ } ,
58+ { //comments
59+ key : 'col3' ,
60+ name : 'Comments' ,
61+ fieldName : 'comments' ,
62+ minWidth : 210 ,
63+ maxWidth : 350 ,
64+ isMultiline : true ,
65+ isRowHeader : true ,
66+ isResizable : true ,
67+ isSorted : false ,
68+ isSortedDescending : false ,
69+ sortAscendingAriaLabel : 'Sorted A to Z' ,
70+ sortDescendingAriaLabel : 'Sorted Z to A' ,
71+ data : 'string' ,
72+ isPadded : true ,
73+ } ,
74+ ] as IColumn [ ] ) ;
75+
76+
77+ // ### LIFECYCLE
78+ useEffect ( ( ) => {
79+ init ( ) ;
80+ } , [ ] ) ;
81+
82+
83+ // ### VARS
84+ // @ts -ignore
85+ const recordGuid :string = _context . page . entityId ;
86+ //@ts -ignore
87+ const entityType :string = _context . page . entityTypeName ;
88+ const lookupSchemaName :string | undefined = _context . parameters . ApprovalId . attributes ?. LogicalName ;
89+ const gridTitle :string = ( _context . parameters . ViewerTitle . raw ! . length !== 0 )
90+ ? `${ _context . parameters . ViewerTitle . raw as string } (${ approvalState } )`
91+ : `APPROVAL PROGRESS SUMMARY (${ approvalState } )` ;
92+ let approvalBag : IResponseBag = {
93+ boundLookupId : '' ,
94+ approvalLookupSchemaName : lookupSchemaName ,
95+ requests : [ ] ,
96+ responses : [ ]
97+ } ;
98+
99+
100+ // ### METHODS
101+ const init = async ( ) => {
102+
103+ //getBoundRecordDetails
104+ await _context . webAPI . retrieveRecord ( entityType , recordGuid , `?$select=_${ approvalBag . approvalLookupSchemaName } _value` ) . then (
105+ function success ( result ) {
106+ approvalBag . boundLookupId = result [ `_${ lookupSchemaName } _value` ] ;
107+ } ,
108+ function error ( error ) {
109+ setIsLoadingState ( false ) ;
110+ console . error ( 'Group Approval Viewer - Retrieve Bound Lookup Error' , error ) ;
111+ }
112+ ) ;
113+
114+ if ( ! approvalBag . boundLookupId ) {
115+ setApprovalState ( 'No Approval Assigned' ) ;
116+ setIsLoadingState ( false ) ;
117+ return ;
118+ } ;
119+
120+ //getApprovalRequests & setApprovalState
121+ await _context . webAPI . retrieveMultipleRecords ( "msdyn_flow_approvalrequest" , `?$filter=_msdyn_flow_approvalrequest_approval_value eq ${ approvalBag . boundLookupId } &$select=_ownerid_value,createdon&$expand=msdyn_flow_approvalrequest_approval($select=msdyn_flow_approval_result)` ) . then (
122+ function success ( result ) {
123+ approvalBag . requests = result . entities ;
124+ if ( approvalBag . requests . length !== 0 ) {
125+ const approvalResult :string | null = approvalBag . requests [ 0 ] . msdyn_flow_approvalrequest_approval . msdyn_flow_approval_result ;
126+ setApprovalState ( ( approvalResult ) ? approvalResult : 'In Progress' ) ;
127+ } ;
128+ } ,
129+ function error ( error ) {
130+ setIsLoadingState ( false ) ;
131+ console . error ( 'Group Approval Viewer - Retrieve Approval Requests Error' , error ) ;
132+ }
133+ ) ;
134+
135+ if ( approvalBag . requests . length === 0 ) {
136+ setIsLoadingState ( false ) ;
137+ setApprovalState ( 'No Requests Found' ) ;
138+ return ;
139+ } ;
140+
141+ //getApprovalResponse
142+ await _context . webAPI . retrieveMultipleRecords ( "msdyn_flow_approvalresponse" , `?$filter=_msdyn_flow_approvalresponse_approval_value eq ${ approvalBag . boundLookupId } &$select=_ownerid_value,createdon,msdyn_flow_approvalresponse_response,msdyn_flow_approvalresponse_comments` ) . then (
143+ function success ( result ) {
144+ approvalBag . responses = result . entities ;
145+ } ,
146+ function error ( error ) {
147+ setIsLoadingState ( false ) ;
148+ console . error ( 'Group Approval Viewer - Retrieve Approval Responses Error' , error ) ;
149+ }
150+ ) ;
151+
152+ transformLoadTableData ( ) ;
153+ } ;
154+
155+ const transformLoadTableData = ( ) => {
156+ const approvalGridArray :ITableRow [ ] = approvalBag . requests . map ( ( e , index ) => {
157+ return {
158+ key : index . toString ( ) ,
159+ 160+ status : getStatusIcon ( e [ '_ownerid_value' ] , approvalBag . responses ) ,
161+ comments : getApproverComments ( e [ '_ownerid_value' ] , approvalBag . responses )
162+ } ;
163+ } ) ;
164+
165+ setTableDataState ( approvalGridArray ) ;
166+ setTableDataMasterState ( approvalGridArray ) ;
167+ setIsLoadingState ( false ) ;
168+ } ;
169+
170+ const getStatusIcon = (
171+ guid :string ,
172+ respArr : ComponentFramework . WebApi . Entity [ ]
173+ ) : JSX . Element | undefined => {
174+ try {
175+ const responseItem = respArr . find ( ( e ) => e [ '_ownerid_value' ] === guid ) ;
176+ if ( responseItem ) {
177+ const responseState = responseItem ! [ 'msdyn_flow_approvalresponse_response' ] . toString ( ) . toLowerCase ( ) ;
178+ switch ( responseState ) {
179+ case 'approve' :
180+ return < Icon className = 'icon icon-approve' iconName = "LikeSolid" /> ;
181+ case 'reject' :
182+ return < Icon className = 'icon icon-reject' iconName = "DislikeSolid" /> ;
183+ }
184+ } else {
185+ return < Icon className = 'icon' iconName = "Clock" />
186+ } ;
187+ }
188+ catch ( error ) {
189+ console . error ( 'Group Approval Viewer - Retrieve Status Icon Error' , error ) ;
190+ }
191+ } ;
192+
193+ const getApproverComments = (
194+ guid :string ,
195+ respArr : ComponentFramework . WebApi . Entity [ ]
196+ ) : string | undefined => {
197+ try {
198+ const responseItem = respArr . find ( ( e ) => e [ '_ownerid_value' ] === guid ) ;
199+ if ( responseItem ) {
200+ return responseItem ! [ 'msdyn_flow_approvalresponse_comments' ] . toString ( ) ;
201+ } else {
202+ return '#N/A'
203+ } ;
204+ }
205+ catch ( error ) {
206+ console . error ( 'Group Approval Viewer - Retrieve Approval Response Error' , error ) ;
207+ }
208+ } ;
209+
210+ const onSearchHandler = (
211+ e : React . FormEvent < HTMLInputElement | HTMLTextAreaElement >
212+ ) : void => {
213+ const query : string = ( e . target as HTMLInputElement ) . value ;
214+ const itemsFiltered = tableDataState . filter ( row => {
215+ return (
216+ row . name ?. toLowerCase ( ) . includes ( query ) ||
217+ row . comments ?. toLowerCase ( ) . includes ( query )
218+ ) ;
219+ } ) ;
220+
221+ setTableDataState ( query ? itemsFiltered : tableDataMasterState ) ;
222+ } ;
223+
224+ const onSortHandler = (
225+ ev ?: React . MouseEvent < HTMLElement > ,
226+ column ?: IColumn
227+ ) : void => {
228+ if ( ! ! column ) {
229+ const newColumns : IColumn [ ] = [ ...columnState ] ;
230+ const currColumn : IColumn = newColumns . filter ( currColumn => column . key === currColumn . key ) [ 0 ] ;
231+ newColumns . forEach ( ( newCol : IColumn ) => {
232+ if ( newCol === currColumn ) {
233+ currColumn . isSortedDescending = ! currColumn . isSortedDescending ;
234+ currColumn . isSorted = true ;
235+ } else {
236+ newCol . isSorted = false ;
237+ newCol . isSortedDescending = true ;
238+ }
239+ } ) ;
240+ const sortedItems = copySort ( tableDataState , currColumn . fieldName ! , currColumn . isSortedDescending ) ;
241+ setTableDataState ( sortedItems ) ;
242+ }
243+ } ;
244+
245+ const copySort = < T extends unknown > (
246+ items : T [ ] ,
247+ columnKey : string ,
248+ isSortedDescending ?: boolean
249+ ) : T [ ] => {
250+ const key = columnKey as keyof T ;
251+ return items . slice ( 0 ) . sort ( ( a : T , b : T ) => ( ( isSortedDescending ? a [ key ] < b [ key ] : a [ key ] > b [ key ] ) ? 1 : - 1 ) ) ;
252+ } ;
253+
254+
255+ // ### COMPONENT
256+ return (
257+ < Fabric >
258+ < div className = 'approval-head' >
259+ < h2 > { gridTitle } </ h2 >
260+ < TextField
261+ placeholder = "Search"
262+ className = 'search-input'
263+ onChange = { onSearchHandler }
264+ />
265+ </ div >
266+ < ShimmeredDetailsList
267+ setKey = "items"
268+ items = { tableDataState }
269+ columns = { columnState }
270+ selectionMode = { SelectionMode . none }
271+ enableShimmer = { isLoadingState }
272+ ariaLabelForShimmer = "Please Wait"
273+ ariaLabelForGrid = "Approval Response Data"
274+ isHeaderVisible = { true }
275+ onColumnHeaderClick = { onSortHandler }
276+ />
277+ </ Fabric >
278+ ) ;
279+ } ;
0 commit comments