1
+ import { useLazyQuery , useMutation } from "@apollo/client"
2
+ import React , { useEffect , useState } from "react"
3
+ import { useTranslation } from "react-i18next"
4
+ import * as XLSX from "xlsx"
5
+ import { ADD_RATINGS_BY_FILE , GET_RATINGS_BY_USER_COHORT } from "../Mutations/Ratings"
6
+ import { toast } from "react-toastify"
7
+ import { GET_TEAMS_BY_USER_ROLE } from "../Mutations/teamMutation"
8
+
9
+ type BulkRatingModalProps = {
10
+ bulkRateModal : boolean ,
11
+ setBulkRateModal : React . Dispatch < React . SetStateAction < boolean > >
12
+ }
13
+
14
+ type AddRatingsByFileFormData = {
15
+ sprint : string ,
16
+ file : File | null ,
17
+ }
18
+
19
+ const BulkRatingModal = ( { bulkRateModal, setBulkRateModal } : BulkRatingModalProps ) => {
20
+ const { t } = useTranslation ( )
21
+ const [ getRatingsByUserCohort , { data : ratings , loading : loadingRatings , error : ratingsError } ] = useLazyQuery ( GET_RATINGS_BY_USER_COHORT , {
22
+ variables : {
23
+ orgToken : localStorage . getItem ( 'orgToken' )
24
+ } ,
25
+ fetchPolicy : 'network-only' ,
26
+ } )
27
+ const [ getTeamsByUserRole , { data : teams , loading : loadingTeams , error : teamsError } ] = useLazyQuery ( GET_TEAMS_BY_USER_ROLE , {
28
+ variables : {
29
+ orgToken : localStorage . getItem ( 'orgToken' )
30
+ } ,
31
+ fetchPolicy : 'network-only' ,
32
+ } )
33
+ const [ addRatingsByFile , { data : bulkRatings , loading : loadingBulkRatings , error : bulkRatingsError } ] = useMutation ( ADD_RATINGS_BY_FILE )
34
+ const [ formData , setFormData ] = useState < AddRatingsByFileFormData > ( {
35
+ sprint : '' ,
36
+ file : null
37
+ } )
38
+ const [ selectedTeam , setSelectedTeam ] = useState < string > ( '' )
39
+
40
+ const saveRatings = async ( e : React . FormEvent ) => {
41
+ try {
42
+ e . preventDefault ( )
43
+ if ( ! formData . sprint ) throw new Error ( "Please select a sprint" )
44
+ if ( ! formData . file ) throw new Error ( "Please select a file" )
45
+ await addRatingsByFile ( {
46
+ variables : {
47
+ file : formData . file ,
48
+ sprint : parseInt ( formData . sprint , 10 ) ,
49
+ orgToken : localStorage . getItem ( 'orgToken' )
50
+ } ,
51
+ } )
52
+ getRatingsByUserCohort ( )
53
+ toast . success ( "Rating completed successfully" )
54
+ } catch ( err : any ) {
55
+ toast . error ( err ?. message )
56
+ }
57
+ }
58
+
59
+ const downloadTeamFile = async ( e : any ) => {
60
+ try {
61
+ if ( selectedTeam === '' ) throw new Error ( "No Team was selected" )
62
+ const team = teams . getTeamsByUserRole . find ( ( team :any ) => team . id === selectedTeam )
63
+ const rows : any = [ ]
64
+ team . members . forEach ( ( member : any ) => {
65
+ if ( member . role === "trainee" ) {
66
+ rows . push ( {
67
+ email : member . email ,
68
+ quantity : '' ,
69
+ quality : '' ,
70
+ professional_skills : '' ,
71
+ feedBacks : ''
72
+ } )
73
+ }
74
+ } )
75
+ const workSheet = rows . length ? XLSX . utils . json_to_sheet ( rows ) : XLSX . utils . json_to_sheet ( [ {
76
+ email : '' ,
77
+ quantity : '' ,
78
+ quality : '' ,
79
+ professional_skills :'' ,
80
+ feedBacks : ''
81
+ } ] )
82
+ const workBook = XLSX . utils . book_new ( )
83
+ workSheet [ "!cols" ] = [ { wch : 20 } ]
84
+ XLSX . utils . book_append_sheet ( workBook , workSheet , "ratings" )
85
+ XLSX . writeFile ( workBook , `${ team . name . replace ( ' ' , '' ) } _Ratings.xlsx` )
86
+ } catch ( err : any ) {
87
+ toast . error ( err ?. message )
88
+ }
89
+ }
90
+
91
+ useEffect ( ( ) => {
92
+ getRatingsByUserCohort ( )
93
+ getTeamsByUserRole ( )
94
+ } , [ ] )
95
+
96
+ return (
97
+ < div className = { `${ bulkRateModal ? "block" : "hidden" } h-screen w-screen z-20 bg-black bg-opacity-30 backdrop-blur-sm fixed top-0 left-0 flex items-center justify-center px-4` } >
98
+ < div className = "w-full p-4 pb-8 bg-indigo-100 rounded-lg dark:bg-dark-bg sm:w-3/4 xl:w-4/12" >
99
+ < div className = "flex flex-wrap items-center justify-center w-full card-title" >
100
+ < h3 className = "w-11/12 text-sm font-bold text-center dark:text-white" >
101
+ { t ( 'Bulk Rating' ) }
102
+ </ h3 >
103
+ < hr className = "w-full my-3 border-b bg-primary" />
104
+ </ div >
105
+ < div >
106
+ < form data-testid = "bulk-rating-form" className = "flex flex-col gap-5" onSubmit = { saveRatings } >
107
+ < div className = "flex flex-col gap-1" >
108
+ < label > Choose a sprint</ label >
109
+ < select data-testid = "select-sprint" className = "p-2 text-black dark:text-white rounded-lg bg-white dark:bg-dark border-2 border-primary"
110
+ defaultValue = { "" }
111
+ onChange = { ( e ) => {
112
+ e . preventDefault ( )
113
+ setFormData ( { ...formData , sprint : e . target . value } )
114
+ } }
115
+ >
116
+ < option > Choose a sprint</ option >
117
+ {
118
+ ratings && ! ratings . getRatingsByUserCohort . length ?
119
+ < option data-testid = "sprint-default-option" value = { 1 } > Sprint 1</ option >
120
+ : ''
121
+ }
122
+ {
123
+ ratings && ratings . getRatingsByUserCohort . length ?
124
+ [ ...ratings . getRatingsByUserCohort ] . map ( ( rating : any ) =>
125
+ < option data-testid = { `sprint-option-${ rating . id } ` } key = { rating . id } value = { rating . sprint } > Sprint { rating . sprint } </ option >
126
+ )
127
+ : ''
128
+ }
129
+ {
130
+ ratings && ratings . getRatingsByUserCohort . length ?
131
+ < option data-testid = "sprint-new-option" value = { [ ...ratings . getRatingsByUserCohort ] . pop ( ) . sprint + 1 } > Sprint { [ ...ratings . getRatingsByUserCohort ] . pop ( ) . sprint + 1 } </ option >
132
+ : ''
133
+ }
134
+ {
135
+ loadingRatings ?
136
+ < option data-testid = "sprint-loading-option" > Loading...</ option >
137
+ : ''
138
+ }
139
+ {
140
+ ratingsError ?
141
+ < option data-testid = "sprint-error-option" > No sprints found...</ option >
142
+ : ''
143
+ }
144
+ </ select >
145
+ </ div >
146
+ < div className = "flex items-center justify-between" >
147
+ < input
148
+ data-testid = "file-input"
149
+ className = "w-1/2 h-full bg-gray-600 rounded-md"
150
+ type = "file"
151
+ onChange = { ( e ) => {
152
+ const file = e . target . files ?. [ 0 ]
153
+ setFormData ( { ...formData , file : file ? file : null } )
154
+ } }
155
+ accept = ".xlsx, .xls"
156
+ >
157
+ </ input >
158
+ < div className = "flex gap-2" >
159
+ < select data-testid = "select-team" className = "p-2 text-sm text-black dark:text-white rounded-lg bg-white dark:bg-dark border-2 border-primary" defaultValue = { "" } onChange = { ( e ) => setSelectedTeam ( e . target . value ) } >
160
+ < option data-testid = "team-default-option" > Choose a team</ option >
161
+ {
162
+ teams && teams . getTeamsByUserRole . length > 0 ?
163
+ teams . getTeamsByUserRole . map ( ( team : any ) => < option data-testid = { `team-option-${ team . id } ` } key = { team . id } value = { team . id } > { team . name } </ option > )
164
+ : ''
165
+ }
166
+ {
167
+ loadingTeams ?
168
+ < option > Loading...</ option >
169
+ : ''
170
+ }
171
+ {
172
+ teamsError ?
173
+ < option > No teams found...</ option >
174
+ : ''
175
+ }
176
+ </ select >
177
+ < button data-testid = "download-button" type = "button" onClick = { downloadTeamFile } className = "p-3 text-white rounded-lg bg-green-500 text-sm font-serif font-semibold" > Download</ button >
178
+ </ div >
179
+ </ div >
180
+
181
+ < div >
182
+ {
183
+ bulkRatings && bulkRatings . addRatingsByFile . RejectedRatings . length > 0 ?
184
+ < div className = "my-1 overflow-x-auto" >
185
+ < table className = "table-fixed min-w-full" >
186
+ < caption className = "caption-top text-left my-2" >
187
+ Rejected Ratings
188
+ </ caption >
189
+ < thead className = "border-b bg-neutral-700 border-neutral-400" >
190
+ < tr >
191
+ < th scope = "col" className = "text-left py-4 px-2" > Email</ th >
192
+ < th scope = "col" className = "text-left py-4 px-2" > Quantity</ th >
193
+ < th scope = "col" className = "text-left py-4 px-2" > Quality</ th >
194
+ < th scope = "col" className = "text-left py-4 px-2" > Professional_Skills</ th >
195
+ < th scope = "col" className = "text-left py-4 px-2" > Feedback</ th >
196
+ </ tr >
197
+ </ thead >
198
+ < tbody >
199
+ { bulkRatings . addRatingsByFile ?. RejectedRatings . map ( ( rating : any , index : number ) =>
200
+ < tr key = { `${ Math . random ( ) * Date . now ( ) } ` } className = "text-red-400" >
201
+ < td className = "text-left py-1 px-2" > { rating . email ? rating . email : "null" } </ td >
202
+ < td className = "text-left py-1 px-2" > { rating . quantity ? rating . quantity : "null" } </ td >
203
+ < td className = "text-left py-1 px-2" > { rating . quality ? rating . quality : "null" } </ td >
204
+ < td className = "text-left py-1 px-2" > { rating . professional_skills ? rating . professional_skills : "null" } </ td >
205
+ < td className = "text-left py-1 px-2" > { rating . feedBacks ? rating . feedBacks : "null" } </ td >
206
+ </ tr >
207
+ ) }
208
+ </ tbody >
209
+ </ table >
210
+ </ div >
211
+ : ''
212
+ }
213
+ </ div >
214
+ < div className = "flex justify-between w-full" >
215
+ < button className = "w-[40%] md:w-1/4 p-3 text-white rounded-lg bg-primary text-sm font-serif font-semibold" type = "button" onClick = { ( ) => setBulkRateModal ( false ) } >
216
+ Cancel
217
+ </ button >
218
+ < button className = "w-[40%] md:w-1/4 p-3 text-white rounded-lg bg-primary text-sm font-serif font-semibold" type = "submit" >
219
+ Save
220
+ </ button >
221
+ </ div >
222
+ </ form >
223
+ </ div >
224
+ </ div >
225
+ </ div >
226
+ )
227
+ }
228
+
229
+ export default BulkRatingModal
0 commit comments