From bd05d16015d4eec71ad2527ce547d8e002fb28a1 Mon Sep 17 00:00:00 2001 From: Shema Date: Mon, 21 Oct 2024 16:08:07 +0200 Subject: [PATCH 1/4] Added BulkRatingModal --- src/components/BulkRatingModal.tsx | 51 +++++++++++++++++++++++++++++ src/pages/AdminTraineeDashboard.tsx | 20 ++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/components/BulkRatingModal.tsx diff --git a/src/components/BulkRatingModal.tsx b/src/components/BulkRatingModal.tsx new file mode 100644 index 00000000..c9e39dcb --- /dev/null +++ b/src/components/BulkRatingModal.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { useTranslation } from "react-i18next" + +type BulkRatingModalProps = { + bulkRateModal: boolean, + setBulkRateModal: React.Dispatch> +} + +const BulkRatingModal = ({ bulkRateModal, setBulkRateModal }: BulkRatingModalProps) => { + const { t } = useTranslation() + return ( +
+
+
+

+ {t('Bulk Rating')} +

+
+
+
+
+
+ + +
+
+ + +
+ + +
+ + +
+
+
+
+
+ ) +} + +export default BulkRatingModal \ No newline at end of file diff --git a/src/pages/AdminTraineeDashboard.tsx b/src/pages/AdminTraineeDashboard.tsx index b6aa471c..4fd2860c 100644 --- a/src/pages/AdminTraineeDashboard.tsx +++ b/src/pages/AdminTraineeDashboard.tsx @@ -42,6 +42,7 @@ import Dropdown from 'react-dropdown-select'; import ViewWeeklyRatings from '../components/ratings/ViewWeeklyRatings'; import { FaTimes } from 'react-icons/fa'; import TtlSkeleton from '../Skeletons/ttl.skeleton'; +import BulkRatingModal from '../components/BulkRatingModal'; const organizationToken = localStorage.getItem('orgToken'); function AdminTraineeDashboard() { @@ -92,6 +93,9 @@ function AdminTraineeDashboard() { // restoreMemberFromCohort const [selectedTraineeId, setSelectedTraineeId] = useState(); + //BulkRatingModal + const [bulkRateModal, setBulkRateModal] = useState(false) + useEffect(() => { const handleClickOutside = (event: any) => { if (modalRef.current && !modalRef.current.contains(event.target)) { @@ -1585,7 +1589,12 @@ function AdminTraineeDashboard() { {/* =========================== End:: RemoveTraineeModel =============================== */} - + {/*============================ Start:: BulkRateModal =================================== */} + + {/*============================ End:: BulkRateModal =================================== */}
@@ -1602,6 +1611,15 @@ function AdminTraineeDashboard() { > {t('add')} +{' '} +
From fbf9229faaffa53c27479e4bc1710d551dd2cbb3 Mon Sep 17 00:00:00 2001 From: Shema Date: Wed, 23 Oct 2024 09:47:52 +0200 Subject: [PATCH 2/4] add fetchSprints query and addRatingsByFile mutation --- src/Mutations/Ratings.tsx | 43 ++++++++++++++++ src/components/BulkRatingModal.tsx | 78 +++++++++++++++++++++++++----- 2 files changed, 109 insertions(+), 12 deletions(-) diff --git a/src/Mutations/Ratings.tsx b/src/Mutations/Ratings.tsx index 78168db1..fb04f1d8 100644 --- a/src/Mutations/Ratings.tsx +++ b/src/Mutations/Ratings.tsx @@ -179,3 +179,46 @@ export const REJECT_RATING = gql` rejectRating(user: $user, sprint: $sprint) } `; + +export const FETCH_SPRINTS = gql` + query fetchSprints($orgToken: String!){ + fetchSprints(orgToken: $orgToken) + } +` + +export const ADD_RATINGS_BY_FILE = gql` + mutation addRatingsByFile($file: Upload!, $sprint: Int!, $orgToken: String!){ + addRatingsByFile(file: $file, sprint: $sprint orgToken: $orgToken){ + NewRatings { + user { + email + } + sprint + phase + quality + quantity + professional_Skills + feedbacks { + sender { + email + } + content + createdAt + } + cohort { + name + } + } + RejectedRatings, + UpdatedRatings { + quantity + quality + professional_Skills + feedbacks { + content + } + oldFeedback + } + } + } +` diff --git a/src/components/BulkRatingModal.tsx b/src/components/BulkRatingModal.tsx index c9e39dcb..82e798d2 100644 --- a/src/components/BulkRatingModal.tsx +++ b/src/components/BulkRatingModal.tsx @@ -1,13 +1,56 @@ -import React from "react" +import { useLazyQuery, useMutation } from "@apollo/client" +import React, { useEffect, useState } from "react" import { useTranslation } from "react-i18next" +import { ADD_RATINGS_BY_FILE, FETCH_SPRINTS } from "../Mutations/Ratings" +import { toast } from "react-toastify" type BulkRatingModalProps = { bulkRateModal: boolean, setBulkRateModal: React.Dispatch> } +type AddRatingsByFileFormData = { + sprint: string, + file: File | null, +} + +const orgToken = localStorage.getItem('orgToken') + const BulkRatingModal = ({ bulkRateModal, setBulkRateModal }: BulkRatingModalProps) => { const { t } = useTranslation() + const [fetchSprints, {data: sprints, loading: loadingSprints, error: sprintsError}] = useLazyQuery(FETCH_SPRINTS,{ + variables:{ + orgToken + }, + fetchPolicy: 'network-only' + }) + const [addRatingsByFile,{data: ratings, loading: loadingRatings, error: ratingsError}] = useMutation(ADD_RATINGS_BY_FILE) + const [formData, setFormData] = useState({ + sprint: '', + file: null + }) + + const saveRatings=async(e: React.FormEvent)=>{ + try{ + e.preventDefault() + await addRatingsByFile({ + variables: { + file:formData.file, + sprint:parseInt(formData.sprint), + orgToken + }, + }) + console.log(ratings) + toast.success("Rating completed susccefully") + }catch(err: any){ + toast.error(err?.message) + } + } + + useEffect(()=>{ + fetchSprints() + },[]) + return (
@@ -18,21 +61,32 @@ const BulkRatingModal = ({ bulkRateModal, setBulkRateModal }: BulkRatingModalPro
-
-
- - -
+
- +
- + { + const file = e.target.files?.[0] + setFormData({...formData, file: file ? file : null}) + }} + > +
- + : '' + }
From 5deff7a056acbaa16cc87327289db901a40e251c Mon Sep 17 00:00:00 2001 From: Shema Date: Fri, 25 Oct 2024 02:00:16 +0200 Subject: [PATCH 4/4] Download team rating template file --- package-lock.json | 61 ++++++++ package.json | 1 + src/Mutations/teamMutation.tsx | 12 ++ src/components/BulkRatingModal.tsx | 215 +++++++++++++++++++---------- 4 files changed, 213 insertions(+), 76 deletions(-) diff --git a/package-lock.json b/package-lock.json index c765241c..f80ca619 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "url-polyfill": "^1.1.12", "vite": "^5.4.7", "vite-tsconfig-paths": "^5.0.1", + "xlsx": "^0.18.5", "zod": "^3.23.8" }, "devDependencies": { @@ -23326,6 +23327,15 @@ "node": ">=0.8" } }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -23503,6 +23513,57 @@ } } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/package.json b/package.json index 39a90777..e626b14c 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "url-polyfill": "^1.1.12", "vite": "^5.4.7", "vite-tsconfig-paths": "^5.0.1", + "xlsx": "^0.18.5", "zod": "^3.23.8" }, "devDependencies": { diff --git a/src/Mutations/teamMutation.tsx b/src/Mutations/teamMutation.tsx index db368f2f..bb1700e3 100644 --- a/src/Mutations/teamMutation.tsx +++ b/src/Mutations/teamMutation.tsx @@ -53,3 +53,15 @@ export const DeleteTeam = gql` } `; +export const GET_TEAMS_BY_ROLE = gql` + query getTeamsByRole($orgToken: String!) { + getTeamsByRole(orgToken: $orgToken){ + id + name + members { + email + role + } + } + } +` diff --git a/src/components/BulkRatingModal.tsx b/src/components/BulkRatingModal.tsx index f325cda1..2d5752b7 100644 --- a/src/components/BulkRatingModal.tsx +++ b/src/components/BulkRatingModal.tsx @@ -1,8 +1,10 @@ import { useLazyQuery, useMutation } from "@apollo/client" import React, { useEffect, useState } from "react" import { useTranslation } from "react-i18next" +import * as XLSX from "xlsx" import { ADD_RATINGS_BY_FILE, FETCH_SPRINTS } from "../Mutations/Ratings" import { toast } from "react-toastify" +import { GET_TEAMS_BY_ROLE } from "../Mutations/teamMutation" type BulkRatingModalProps = { bulkRateModal: boolean, @@ -18,40 +20,83 @@ const orgToken = localStorage.getItem('orgToken') const BulkRatingModal = ({ bulkRateModal, setBulkRateModal }: BulkRatingModalProps) => { const { t } = useTranslation() - const [fetchSprints, {data: sprints, loading: loadingSprints, error: sprintsError}] = useLazyQuery(FETCH_SPRINTS,{ - variables:{ + const [fetchSprints, { data: sprints, loading: loadingSprints, error: sprintsError }] = useLazyQuery(FETCH_SPRINTS, { + variables: { orgToken }, - fetchPolicy: 'network-only' + fetchPolicy: 'network-only', }) - const [addRatingsByFile,{data: ratings, loading: loadingRatings, error: ratingsError}] = useMutation(ADD_RATINGS_BY_FILE) + const [getTeamsByRole, {data: teams, loading: loadingTeams, error: teamsError}] = useLazyQuery(GET_TEAMS_BY_ROLE, { + variables: { + orgToken + }, + fetchPolicy: 'network-only', + }) + const [addRatingsByFile, { data: ratings, loading: loadingRatings, error: ratingsError }] = useMutation(ADD_RATINGS_BY_FILE) const [formData, setFormData] = useState({ sprint: '', file: null }) + const [selectedTeam, setSelectedTeam] = useState('') - const saveRatings=async(e: React.FormEvent)=>{ - try{ + const saveRatings = async (e: React.FormEvent) => { + try { e.preventDefault() + if(formData.sprint === '') throw new Error("Please select a sprint") + if(!formData.file) throw new Error("Please select a file") await addRatingsByFile({ variables: { - file:formData.file, - sprint:parseInt(formData.sprint), + file: formData.file, + sprint: parseInt(formData.sprint), orgToken }, }) + fetchSprints() toast.success("Rating completed succefully") + } catch (err: any) { + toast.error(err?.message) + } + } + + const downloadTeamFile = async(e: any)=>{ + try{ + if(selectedTeam === '') throw new Error("No Team was selected") + const team = teams.getTeamsByRole.find((team:any)=>team.id === selectedTeam) + const rows: any = [] + team.members.forEach((member: any)=>{ + console.log(member) + if(member.role !== "trainee") return + rows.push({ + email: member.email, + quantity: '', + quality: '', + professional_skills:'', + feedBacks: '' + }) + }) + const workSheet = rows.length ? XLSX.utils.json_to_sheet(rows) : XLSX.utils.json_to_sheet([{ + email: '', + quantity: '', + quality: '', + professional_skills:'', + feedBacks: '' + }]) + const workBook = XLSX.utils.book_new() + workSheet["!cols"] = [ { wch: 20 } ] + XLSX.utils.book_append_sheet(workBook, workSheet,"ratings") + XLSX.writeFile(workBook, `${team.name.replace(' ','')}_Ratings.xlsx`) }catch(err: any){ toast.error(err?.message) } } - useEffect(()=>{ + useEffect(() => { fetchSprints() - },[]) + getTeamsByRole() + }, []) return ( -
+

@@ -62,75 +107,93 @@ const BulkRatingModal = ({ bulkRateModal, setBulkRateModal }: BulkRatingModalPro
- - + + +
+
+ { + const file = e.target.files?.[0] + setFormData({ ...formData, file: file ? file : null }) + }} + > + +
+ + +
- { - const file = e.target.files?.[0] - setFormData({...formData, file: file ? file : null}) - }} - > -
{ - ratings && ratings.addRatingsByFile.RejectedRatings.length > 0? -
- - - - - - - - - - - - - {ratings.addRatingsByFile?.RejectedRatings.map((rating: any, index: number)=> - - - - - - - - )} - -
- Rejected Ratings -
EmailQuantityQualityProfessional_SkillsFeedback
{rating.email ? rating.email : "null"}{rating.quantity ? rating.quantity : "null"}{rating.quality ? rating.quality : "null"}{rating.professional_skills ? rating.professional_skills : "null"}{rating.feedBacks ? rating.feedBacks : "null"}
-
- : '' + ratings && ratings.addRatingsByFile.RejectedRatings.length > 0 ? +
+ + + + + + + + + + + + + {ratings.addRatingsByFile?.RejectedRatings.map((rating: any, index: number) => + + + + + + + + )} + +
+ Rejected Ratings +
EmailQuantityQualityProfessional_SkillsFeedback
{rating.email ? rating.email : "null"}{rating.quantity ? rating.quantity : "null"}{rating.quality ? rating.quality : "null"}{rating.professional_skills ? rating.professional_skills : "null"}{rating.feedBacks ? rating.feedBacks : "null"}
+
+ : '' }