Skip to content

Commit 2f3b115

Browse files
committed
Added BulkRatingModal
1 parent df093d2 commit 2f3b115

20 files changed

+4302
-918
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
@@ -113,6 +113,7 @@
113113
"url-polyfill": "^1.1.12",
114114
"vite": "^5.4.7",
115115
"vite-tsconfig-paths": "^5.0.1",
116+
"xlsx": "^0.18.5",
116117
"zod": "^3.23.8"
117118
},
118119
"devDependencies": {

src/Mutations/Ratings.tsx

+52
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,55 @@ export const REJECT_RATING = gql`
179179
rejectRating(user: $user, sprint: $sprint)
180180
}
181181
`;
182+
183+
export const GET_RATINGS_BY_USER_COHORT = gql`
184+
query getRatingsByUserCohort($orgToken: String!){
185+
getRatingsByUserCohort(orgToken: $orgToken){
186+
id
187+
sprint
188+
}
189+
}
190+
`
191+
192+
export const ADD_RATINGS_BY_FILE = gql`
193+
mutation addRatingsByFile($file: Upload!, $sprint: Int!, $orgToken: String!){
194+
addRatingsByFile(file: $file, sprint: $sprint orgToken: $orgToken){
195+
NewRatings {
196+
user {
197+
email
198+
}
199+
sprint
200+
phase
201+
quality
202+
quantity
203+
professional_Skills
204+
feedbacks {
205+
sender {
206+
email
207+
}
208+
content
209+
createdAt
210+
}
211+
cohort {
212+
name
213+
}
214+
}
215+
RejectedRatings{
216+
email
217+
quantity
218+
quality
219+
professional_skills
220+
feedBacks
221+
}
222+
UpdatedRatings {
223+
quantity
224+
quality
225+
professional_Skills
226+
feedbacks {
227+
content
228+
}
229+
oldFeedback
230+
}
231+
}
232+
}
233+
`

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

+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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+
console.log(member)
66+
if (member.role === "trainee") {
67+
rows.push({
68+
email: member.email,
69+
quantity: '',
70+
quality: '',
71+
professional_skills: '',
72+
feedBacks: ''
73+
})
74+
}
75+
})
76+
const workSheet = rows.length ? XLSX.utils.json_to_sheet(rows) : XLSX.utils.json_to_sheet([{
77+
email: '',
78+
quantity: '',
79+
quality: '',
80+
professional_skills:'',
81+
feedBacks: ''
82+
}])
83+
const workBook = XLSX.utils.book_new()
84+
workSheet["!cols"] = [ { wch: 20 } ]
85+
XLSX.utils.book_append_sheet(workBook, workSheet,"ratings")
86+
XLSX.writeFile(workBook, `${team.name.replace(' ','')}_Ratings.xlsx`)
87+
}catch(err: any){
88+
toast.error(err?.message)
89+
}
90+
}
91+
92+
useEffect(() => {
93+
getRatingsByUserCohort()
94+
getTeamsByUserRole()
95+
}, [])
96+
97+
return (
98+
<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`}>
99+
<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">
100+
<div className="flex flex-wrap items-center justify-center w-full card-title">
101+
<h3 className="w-11/12 text-sm font-bold text-center dark:text-white">
102+
{t('Bulk Rating')}
103+
</h3>
104+
<hr className="w-full my-3 border-b bg-primary" />
105+
</div>
106+
<div>
107+
<form data-testid="bulk-rating-form" className="flex flex-col gap-5" onSubmit={saveRatings}>
108+
<div className="flex flex-col gap-1">
109+
<label>Choose a sprint</label>
110+
<select data-testid="select-sprint" className="p-2 text-black dark:text-white rounded-lg bg-white dark:bg-dark border-2 border-primary"
111+
defaultValue={""}
112+
onChange={(e) => {
113+
e.preventDefault()
114+
setFormData({ ...formData, sprint: e.target.value })
115+
}}
116+
>
117+
<option>Choose a sprint</option>
118+
{
119+
ratings && !ratings.getRatingsByUserCohort.length ?
120+
<option data-testid="sprint-default-option" value={1}>Sprint 1</option>
121+
: ''
122+
}
123+
{
124+
ratings && ratings.getRatingsByUserCohort.length ?
125+
[...ratings.getRatingsByUserCohort].map((rating: any) =>
126+
<option data-testid={`sprint-option-${rating.id}`} key={rating.id} value={rating.sprint}>Sprint {rating.sprint}</option>
127+
)
128+
: ''
129+
}
130+
{
131+
ratings && ratings.getRatingsByUserCohort.length ?
132+
<option data-testid="sprint-new-option" value={[...ratings.getRatingsByUserCohort].pop().sprint+1}>Sprint {[...ratings.getRatingsByUserCohort].pop().sprint+1}</option>
133+
: ''
134+
}
135+
{
136+
loadingRatings ?
137+
<option data-testid="sprint-loading-option">Loading...</option>
138+
: ''
139+
}
140+
{
141+
ratingsError ?
142+
<option data-testid="sprint-error-option">No sprints found...</option>
143+
: ''
144+
}
145+
</select>
146+
</div>
147+
<div className="flex items-center justify-between">
148+
<input
149+
data-testid="file-input"
150+
className="w-1/2 h-full bg-gray-600 rounded-md"
151+
type="file"
152+
onChange={(e) => {
153+
const file = e.target.files?.[0]
154+
setFormData({ ...formData, file: file ? file : null })
155+
}}
156+
accept=".xlsx, .xls"
157+
>
158+
</input>
159+
<div className="flex gap-2">
160+
<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)}>
161+
<option data-testid="team-default-option">Choose a team</option>
162+
{
163+
teams && teams.getTeamsByUserRole.length > 0 ?
164+
teams.getTeamsByUserRole.map((team: any)=><option data-testid={`team-option-${team.id}`} key={team.id} value={team.id}>{team.name}</option>)
165+
: ''
166+
}
167+
{
168+
loadingTeams ?
169+
<option>Loading...</option>
170+
: ''
171+
}
172+
{
173+
teamsError ?
174+
<option>No teams found...</option>
175+
: ''
176+
}
177+
</select>
178+
<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>
179+
</div>
180+
</div>
181+
182+
<div>
183+
{
184+
bulkRatings && bulkRatings.addRatingsByFile.RejectedRatings.length > 0 ?
185+
<div className="my-1 overflow-x-auto">
186+
<table className="table-fixed min-w-full">
187+
<caption className="caption-top text-left my-2">
188+
Rejected Ratings
189+
</caption>
190+
<thead className="border-b bg-neutral-700 border-neutral-400">
191+
<tr>
192+
<th scope="col" className="text-left py-4 px-2">Email</th>
193+
<th scope="col" className="text-left py-4 px-2">Quantity</th>
194+
<th scope="col" className="text-left py-4 px-2">Quality</th>
195+
<th scope="col" className="text-left py-4 px-2">Professional_Skills</th>
196+
<th scope="col" className="text-left py-4 px-2">Feedback</th>
197+
</tr>
198+
</thead>
199+
<tbody>
200+
{bulkRatings.addRatingsByFile?.RejectedRatings.map((rating: any, index: number) =>
201+
<tr key={index} className="text-red-400">
202+
<td className="text-left py-1 px-2">{rating.email ? rating.email : "null"}</td>
203+
<td className="text-left py-1 px-2">{rating.quantity ? rating.quantity : "null"}</td>
204+
<td className="text-left py-1 px-2">{rating.quality ? rating.quality : "null"}</td>
205+
<td className="text-left py-1 px-2">{rating.professional_skills ? rating.professional_skills : "null"}</td>
206+
<td className="text-left py-1 px-2">{rating.feedBacks ? rating.feedBacks : "null"}</td>
207+
</tr>
208+
)}
209+
</tbody>
210+
</table>
211+
</div>
212+
: ''
213+
}
214+
</div>
215+
<div className="flex justify-between w-full">
216+
<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)}>
217+
Cancel
218+
</button>
219+
<button className="w-[40%] md:w-1/4 p-3 text-white rounded-lg bg-primary text-sm font-serif font-semibold" type="submit">
220+
Save
221+
</button>
222+
</div>
223+
</form>
224+
</div>
225+
</div>
226+
</div>
227+
)
228+
}
229+
230+
export default BulkRatingModal

0 commit comments

Comments
 (0)