Skip to content

Commit ec4a893

Browse files
committed
Added BulkRatingModal
1 parent fe125aa commit ec4a893

12 files changed

+850
-27
lines changed

package-lock.json

+61
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
"url-polyfill": "^1.1.12",
115115
"vite": "^5.4.7",
116116
"vite-tsconfig-paths": "^5.0.1",
117+
"xlsx": "^0.18.5",
117118
"zod": "^3.23.8"
118119
},
119120
"devDependencies": {

src/Mutations/Ratings.tsx

+52
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,55 @@ export const REJECT_RATING = gql`
149149
rejectRating(user: $user, sprint: $sprint)
150150
}
151151
`;
152+
153+
export const GET_RATINGS_BY_USER_COHORT = gql`
154+
query getRatingsByUserCohort($orgToken: String!){
155+
getRatingsByUserCohort(orgToken: $orgToken){
156+
id
157+
sprint
158+
}
159+
}
160+
`
161+
162+
export const ADD_RATINGS_BY_FILE = gql`
163+
mutation addRatingsByFile($file: Upload!, $sprint: Int!, $orgToken: String!){
164+
addRatingsByFile(file: $file, sprint: $sprint orgToken: $orgToken){
165+
NewRatings {
166+
user {
167+
email
168+
}
169+
sprint
170+
phase
171+
quality
172+
quantity
173+
professional_Skills
174+
feedbacks {
175+
sender {
176+
email
177+
}
178+
content
179+
createdAt
180+
}
181+
cohort {
182+
name
183+
}
184+
}
185+
RejectedRatings{
186+
email
187+
quantity
188+
quality
189+
professional_skills
190+
feedBacks
191+
}
192+
UpdatedRatings {
193+
quantity
194+
quality
195+
professional_Skills
196+
feedbacks {
197+
content
198+
}
199+
oldFeedback
200+
}
201+
}
202+
}
203+
`

src/Mutations/teamMutation.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,15 @@ export const DeleteTeam = gql`
5353
}
5454
`;
5555

56+
export const GET_TEAMS_BY_USER_ROLE = gql`
57+
query getTeamsByUserRole($orgToken: String!) {
58+
getTeamsByUserRole(orgToken: $orgToken){
59+
id
60+
name
61+
members {
62+
email
63+
role
64+
}
65+
}
66+
}
67+
`

src/components/BulkRatingModal.tsx

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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

Comments
 (0)