From a26089e16a92c2b74a0e6f1f7f4230860eb6e8b1 Mon Sep 17 00:00:00 2001 From: Andrew Izvarin Date: Thu, 10 Oct 2024 14:22:38 +0300 Subject: [PATCH 1/3] KB-111 Lecturer public rating --- src/@types/index.d.ts | 19 +++- src/api/teacher.ts | 10 +- src/components/Avatar/Avatar.tsx | 21 +++-- src/components/Ratings/Ratings.tsx | 36 +++++++ src/components/TabList/TabList.tsx | 22 ++--- src/constants.ts | 3 +- src/pages/profile/[teacherId].tsx | 147 ++++++++++++++++------------- src/styles/global.css | 5 - 8 files changed, 167 insertions(+), 96 deletions(-) create mode 100644 src/components/Ratings/Ratings.tsx diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index 7cffa3c..26eee04 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -24,7 +24,12 @@ declare namespace ECampus { interface PaginationModel { pageNumber: number; pageCount: number; - } + }; + + type Subdivision = { + id: number; + name: string; + }; } declare namespace Intellect { @@ -60,7 +65,7 @@ declare namespace Intellect { }; }; - type ExperienceType = 'publications' | 'exploration' | 'exploration_results' | 'confs' | 'profile'; + type ExperienceType = 'publications' | 'exploration' | 'exploration_results' | 'confs' | 'profile' | 'rating'; type SearchMode = 'overall' | 'alphabetic' | 'subdivision' | 'interests'; type SearchParams = 'startsWith' | 'subdivision' | 'interests'; @@ -68,6 +73,16 @@ declare namespace Intellect { [key in ExperienceType]: ExperienceItem; }; + + type Rating = { + subdivision: ECampus.Subdivision; + overallRating?: number; + studyYear: string; + educationalMethodologicalRating?: number; + scientificInnovativeRating?: number; + organizationalEducationalRating?: number; + }; + type Tab = { label: string; type: Intellect.SearchMode; diff --git a/src/api/teacher.ts b/src/api/teacher.ts index 2c3d892..7533bac 100644 --- a/src/api/teacher.ts +++ b/src/api/teacher.ts @@ -1,4 +1,5 @@ import Http, { API_BASE_URL } from './index'; + import { parseSearchParams } from '@/utils'; type ExperienceResultPromise = Promise>; @@ -28,7 +29,7 @@ const getKRExecutions = (teacherId: string): ExperienceResultPromise => { return Http.get(`/v2/persons/${teacherId}/researches/carrying-out`); }; -const getKRResults = (teacherId: string, key: Intellect.ExperienceType): ExperienceResultPromise => { +const getKRResults = (teacherId: string): ExperienceResultPromise => { return Http.get(`/v2/persons/${teacherId}/researches/results`); }; @@ -44,7 +45,7 @@ export const getExperienceByTeacherId = async (teacherId: string): Promise([ getPublications(teacherId), getKRExecutions(teacherId), - getKRResults(teacherId, 'exploration_results'), + getKRResults(teacherId), getConferences(teacherId), ]); @@ -67,8 +68,13 @@ export const getInterests = (limit?: number): Promise => { return Http.get('/v2/scientific-interests' + param); }; +export const getRatings = (teacherId: string): Promise => { + return Http.get(`/v2/persons/${teacherId}/rating`); +}; + export default { getExperienceByTeacherId, getTeacherByTeacherId, getInterests, + getRatings, }; diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index d50e806..59af573 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import Image from 'next/image'; - +import React from 'react'; import avatarStub from '@/assets/img/avatar-stub.png'; type Props = { @@ -9,14 +8,16 @@ type Props = { const Avatar: React.FC = ({ img }) => { return ( - avatar +
+ avatar +
); }; diff --git a/src/components/Ratings/Ratings.tsx b/src/components/Ratings/Ratings.tsx new file mode 100644 index 0000000..5c59b4d --- /dev/null +++ b/src/components/Ratings/Ratings.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +interface RatingsProps { + ratings: Intellect.Rating[]; +} + +export const Ratings = ({ ratings }: RatingsProps) => { + return ( +
+ + + + + + + + + + + + + {ratings.map(rating => ( + + + + + + + + + ))} + +
Навчальний рікРейтинг з навчально-методичної роботиРейтинг з науково-інноваційної роботиРейтинг з організаційно-виховної роботиЗагальний рейтингКафедра
{rating.studyYear}{rating.educationalMethodologicalRating}{rating.scientificInnovativeRating}{rating.organizationalEducationalRating}{rating.overallRating}{rating.subdivision.name}
+
+ ); +}; diff --git a/src/components/TabList/TabList.tsx b/src/components/TabList/TabList.tsx index 39fd72c..d77bc98 100644 --- a/src/components/TabList/TabList.tsx +++ b/src/components/TabList/TabList.tsx @@ -1,34 +1,32 @@ import styles from './TabList.module.css'; import React from 'react'; -type Props = { +interface TabListProps { children: React.ReactNode; - tabs: Record; - selectTab?: (a: any) => void; + tabs: Record; + selectTab: (tab: T) => void; className: string; - tabActive: any; + tabActive: T; }; -const TabList: React.FC = ({ +const TabList = ({ children, tabs, - selectTab = (newTab: any) => { - console.log('SelectTab received ' + newTab); - }, + selectTab, className = '', tabActive, -}) => { +}: TabListProps) => { return (
-
+
{Object.keys(tabs).map((tab) => (
selectTab(tab)} + onClick={() => selectTab(tab as T)} key={tab} > - {tabs[tab as Intellect.ExperienceType]} + {tabs[tab as T]}
))}
diff --git a/src/constants.ts b/src/constants.ts index 99627ca..3774fd6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,5 @@ -import { getInterests } from '@/api/teacher'; import { getFaculties } from '@/api/subdivision'; +import { getInterests } from '@/api/teacher'; export const experienceTabs: Record = { profile: 'Профіль', @@ -7,6 +7,7 @@ export const experienceTabs: Record = { exploration: 'Виконання науково-дослідних та дослідно-конструкторських робіт', exploration_results: 'Результати виконання науково-дослідних та дослідно-конструкторських робіт', confs: 'Конференції, виставки', + rating: 'Рейтинг', }; export const searchStringParams = { diff --git a/src/pages/profile/[teacherId].tsx b/src/pages/profile/[teacherId].tsx index 97158dd..f425883 100644 --- a/src/pages/profile/[teacherId].tsx +++ b/src/pages/profile/[teacherId].tsx @@ -9,11 +9,12 @@ import TabList from '@/components/TabList/TabList'; import Avatar from '@/components/Avatar/Avatar'; import IProfileDetails from '@/components/I-ProfileDetails/I-ProfileDetails'; import ShareProfile from '@/components/ShareProfile/ShareProfile'; +import { Ratings } from '@/components/Ratings/Ratings'; import useLinkRoute from '@/utils/hooks/useLinkRoute'; import { experienceTabs } from '@/constants'; -import Http, { API_BASE_URL } from '@/api/index'; -import { getExperienceByTeacherId, getTeacherByTeacherId } from '@/api/teacher'; +import { API_BASE_URL } from '@/api/index'; +import { getExperienceByTeacherId, getRatings, getTeacherByTeacherId } from '@/api/teacher'; /** * @description Fetches teacher and experience data on server side. @@ -24,11 +25,18 @@ import { getExperienceByTeacherId, getTeacherByTeacherId } from '@/api/teacher'; export async function getServerSideProps(context: any) { const teacherId = context.params.teacherId; - const teacher = await getTeacherByTeacherId(teacherId); - const experience = await getExperienceByTeacherId(teacherId); + const [ + teacher, + experience, + ratings + ] = await Promise.all([ + getTeacherByTeacherId(teacherId), + getExperienceByTeacherId(teacherId), + getRatings(teacherId), + ]); return { - props: { teacher, experience }, // will be passed to the page component as props + props: { teacher, experience, ratings }, }; } @@ -39,23 +47,23 @@ export async function getServerSideProps(context: any) { * @returns */ const generateMetaDescription = (teacher: Intellect.Teacher | null): string => { - if (teacher) { - const credoOrEmpty = teacher.credo ? `"${teacher.credo}", ` : ''; // with quotes - const academicDegreeOrEmpty = teacher.academicDegree ? `${teacher.academicDegree}, ` : ''; - const positionsOrEmpty = - teacher.positions?.length && `${teacher.positions.map((p) => `${p.name}, ${p.subdivision.name}`)}, `; - const scientificInterestsOrEmpty = teacher.scientificInterest ? `${teacher.scientificInterest}` : ''; - - const finalDescription = credoOrEmpty + academicDegreeOrEmpty + positionsOrEmpty + scientificInterestsOrEmpty; + if (!teacher) { + return ''; + } + + const credoOrEmpty = teacher.credo ? `"${teacher.credo}", ` : ''; // with quotes + const academicDegreeOrEmpty = teacher.academicDegree ? `${teacher.academicDegree}, ` : ''; + const positionsOrEmpty = + teacher.positions?.length && `${teacher.positions.map((p) => `${p.name}, ${p.subdivision.name}`)}, `; + const scientificInterestsOrEmpty = teacher.scientificInterest ? `${teacher.scientificInterest}` : ''; - if (finalDescription.endsWith(', ')) { - return finalDescription.slice(0, -2); - } + const finalDescription = credoOrEmpty + academicDegreeOrEmpty + positionsOrEmpty + scientificInterestsOrEmpty; - return finalDescription; + if (finalDescription.endsWith(', ')) { + return finalDescription.slice(0, -2); } - return ''; + return finalDescription; }; /** @@ -66,9 +74,11 @@ const generateMetaDescription = (teacher: Intellect.Teacher | null): string => { function ITeacherInfo({ teacher, experience, + ratings, }: { teacher: Intellect.Teacher | null; experience: Intellect.TeacherExperience | null; + ratings: Intellect.Rating[] | null, }) { const [activeTab, setActiveTab] = useState( Object.keys(experienceTabs)[0] as Intellect.ExperienceType @@ -83,44 +93,59 @@ function ITeacherInfo({ }, [teacher, experience]); const generateDataPerTab = (): React.JSX.Element[] => { - if (experience) { - const selectedExperience = experience[activeTab]; - const experienceItemKeys = Object.keys(selectedExperience) || []; - - return (experienceItemKeys || []).map((experienceItemKey: string, idx: number) => ( -
-

{experienceItemKey}

- {Object.keys(selectedExperience[experienceItemKey]).map((key, idx) => ( -
-

{key}

- - {(selectedExperience[experienceItemKey][key] || []).map((data) => { - return ( -
- {(data.value || []).map((publication: string, idx: number) => ( -

'), - }} - /> - ))} -

- ); - })} -
-
- ))} -
- )); + if (!experience) { + return []; } - return []; + const selectedExperience = experience[activeTab]; + const experienceItemKeys = Object.keys(selectedExperience) || []; + + return (experienceItemKeys || []).map((experienceItemKey: string, idx: number) => ( +
+

{experienceItemKey}

+ {Object.keys(selectedExperience[experienceItemKey]).map((key, idx) => ( +
+

{key}

+ + {(selectedExperience[experienceItemKey][key] || []).map((data) => { + return ( +
+ {(data.value || []).map((publication: string, idx: number) => ( +

'), + }} + /> + ))} +

+ ); + })} +
+
+ ))} +
+ )); }; const description = generateMetaDescription(teacher); + const renderTab = (tab: Intellect.ExperienceType) => { + switch (tab) { + case 'profile': + return teacher ? : null; + case 'rating': + return ratings ? : null; + default: + return ( + + {generateDataPerTab()} + + ); + } + }; + return ( <> @@ -138,24 +163,24 @@ function ITeacherInfo({
-
-
+
+
{teacher ? : null}
-
- +
+ {teacher?.fullName} {teacher?.credo ? ( -
+
{teacher?.credo}
) : null} -
- {(teacher?.positions || []).map((item: any, idx) => ( +
+ {(teacher?.positions || []).map((item: Intellect.Position) => ( - {activeTab !== 'profile' ? ( - - {generateDataPerTab()} - - ) : teacher ? ( - - ) : null} + {renderTab(activeTab)}
diff --git a/src/styles/global.css b/src/styles/global.css index 472462c..9994228 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -66,11 +66,6 @@ footer { display: none; } -.avatar { - border: 1px solid #EEE; - border-radius: 5px; -} - footer a { text-decoration: underline; } From 6456138ed24edfec2c3c63c2ce98e12ac070cf2c Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 10 Oct 2024 15:51:32 +0300 Subject: [PATCH 2/3] Add description for rating --- src/components/Ratings/Ratings.tsx | 55 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/components/Ratings/Ratings.tsx b/src/components/Ratings/Ratings.tsx index 5c59b4d..d7f9b2d 100644 --- a/src/components/Ratings/Ratings.tsx +++ b/src/components/Ratings/Ratings.tsx @@ -6,31 +6,38 @@ interface RatingsProps { export const Ratings = ({ ratings }: RatingsProps) => { return ( -
- - - - - - - - - - - - - {ratings.map(rating => ( - - - - - - - +
+

+ Рейтингове оцінювання професійної діяльності науково-педагогічних працівників є складовою + внутрішньої системи забезпечення якості вищої освіти та освітньої діяльності університету. +

+ +
+
Навчальний рікРейтинг з навчально-методичної роботиРейтинг з науково-інноваційної роботиРейтинг з організаційно-виховної роботиЗагальний рейтингКафедра
{rating.studyYear}{rating.educationalMethodologicalRating}{rating.scientificInnovativeRating}{rating.organizationalEducationalRating}{rating.overallRating}{rating.subdivision.name}
+ + + + + + + + - ))} - -
Навчальний рікРейтинг з навчально-методичної роботиРейтинг з науково-інноваційної роботиРейтинг з організаційно-виховної роботиЗагальний рейтингКафедра
+ + + {ratings.map(rating => ( + + {rating.studyYear} + {rating.educationalMethodologicalRating} + {rating.scientificInnovativeRating} + {rating.organizationalEducationalRating} + {rating.overallRating} + {rating.subdivision.name} + + ))} + + +
); }; From 2aafb62b68dae1b7bbc7e602d02b3aa38d853f47 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 10 Oct 2024 15:57:01 +0300 Subject: [PATCH 3/3] Add legend --- src/components/Ratings/Ratings.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/Ratings/Ratings.tsx b/src/components/Ratings/Ratings.tsx index d7f9b2d..20e076c 100644 --- a/src/components/Ratings/Ratings.tsx +++ b/src/components/Ratings/Ratings.tsx @@ -17,9 +17,9 @@ export const Ratings = ({ ratings }: RatingsProps) => { Навчальний рік - Рейтинг з навчально-методичної роботи - Рейтинг з науково-інноваційної роботи - Рейтинг з організаційно-виховної роботи + НМР + НIР + ОВР Загальний рейтинг Кафедра @@ -38,6 +38,12 @@ export const Ratings = ({ ratings }: RatingsProps) => {
+ +

+ НМР – рейтинг з навчально-методичної роботи
+ НIР - рейтинг з науково-інноваційної роботи
+ ОВР - рейтинг з організаційно-виховної роботи
+

); };