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 ) ,
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 = { index } 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