-
-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Functionality for Mentors to Edit Feedback for Students (#2512
- Loading branch information
1 parent
fa105d8
commit 677142b
Showing
10 changed files
with
308 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
185 changes: 185 additions & 0 deletions
185
client/src/modules/Feedback/components/FeedbackForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import { Alert, Button, Col, Form, Input, message, Radio, Rate, Row, Typography } from 'antd'; | ||
import { | ||
CreateStudentFeedbackDto, | ||
CreateStudentFeedbackDtoEnglishLevelEnum as EnglishLevelEnum, | ||
MentorStudentDto, | ||
CreateStudentFeedbackDtoRecommendationEnum as RecommendationEnum, | ||
SoftSkillEntryIdEnum, | ||
} from 'api'; | ||
import { UserSearch } from 'components/UserSearch'; | ||
import { useEffect } from 'react'; | ||
import { convertSoftSkillValueToEnum, softSkills, softSkillValues } from '../data/softSkills'; | ||
import { useRouter } from 'next/router'; | ||
|
||
type FormValues = Record<SoftSkillEntryIdEnum, number> & { | ||
studentId: number; | ||
suggestions: string; | ||
recommendation: RecommendationEnum; | ||
recommendationComment: string; | ||
englishLevel: EnglishLevelEnum; | ||
}; | ||
|
||
const englishLevels = [ | ||
EnglishLevelEnum.A1, | ||
EnglishLevelEnum.A2, | ||
EnglishLevelEnum.B1, | ||
EnglishLevelEnum.B2, | ||
EnglishLevelEnum.C1, | ||
EnglishLevelEnum.C2, | ||
]; | ||
|
||
type FeedbackFormProps = { | ||
studentId: number; | ||
students?: MentorStudentDto[]; | ||
onSubmit: (studentId: number, payload: CreateStudentFeedbackDto, existingFeedbackId?: number) => Promise<void>; | ||
}; | ||
|
||
const { TextArea } = Input; | ||
|
||
const getInitialValues = (studentId: number, students: MentorStudentDto[] | undefined): FormValues | undefined => { | ||
const selectedStudent = students?.find(student => student.id === studentId); | ||
if (selectedStudent) { | ||
const feedback = selectedStudent.feedbacks[0]; | ||
if (feedback) { | ||
return { | ||
studentId, | ||
suggestions: feedback.content.suggestions, | ||
recommendation: feedback.recommendation, | ||
recommendationComment: feedback.content.recommendationComment, | ||
englishLevel: feedback.englishLevel, | ||
...feedback.content.softSkills.reduce( | ||
(acc, { id, value }) => { | ||
acc[id as SoftSkillEntryIdEnum] = softSkillValues[value]; | ||
return acc; | ||
}, | ||
{} as Record<SoftSkillEntryIdEnum, number>, | ||
), | ||
}; | ||
} | ||
} | ||
return { studentId } as FormValues; | ||
}; | ||
|
||
export const FeedbackForm = ({ studentId, onSubmit, students }: FeedbackFormProps) => { | ||
const router = useRouter(); | ||
const [form] = Form.useForm<FormValues>(); | ||
|
||
useEffect(() => { | ||
const initialValues = getInitialValues(studentId, students); | ||
if (initialValues) { | ||
form.setFieldsValue(initialValues); | ||
} | ||
}, [studentId, students, form]); | ||
|
||
const handleStudentChange = (value: string) => { | ||
const newStudentId = Number(value); | ||
const initialValues = getInitialValues(newStudentId, students); | ||
if (initialValues) { | ||
form.resetFields(); | ||
form.setFieldsValue(initialValues); | ||
} else { | ||
form.resetFields(); | ||
form.setFieldsValue({ studentId: newStudentId }); | ||
} | ||
router.push( | ||
{ | ||
pathname: router.pathname, | ||
query: { ...router.query, studentId: newStudentId }, | ||
}, | ||
undefined, | ||
{ shallow: true }, | ||
); | ||
}; | ||
|
||
const handleSubmit = async (values: FormValues) => { | ||
try { | ||
const { studentId, ...rest } = values; | ||
|
||
const payload: CreateStudentFeedbackDto = { | ||
recommendation: rest.recommendation, | ||
content: { | ||
suggestions: rest.suggestions ?? '', | ||
recommendationComment: rest.recommendationComment, | ||
softSkills: softSkills.map(({ id }) => ({ id, value: convertSoftSkillValueToEnum(rest[id]) })), | ||
}, | ||
englishLevel: rest.englishLevel ? rest.englishLevel : EnglishLevelEnum.Unknown, | ||
}; | ||
|
||
const selectedStudent = students?.find(student => student.id === studentId); | ||
const existingFeedback = selectedStudent?.feedbacks[0]; | ||
|
||
await onSubmit(studentId, payload, existingFeedback ? existingFeedback.id : undefined); | ||
} catch (e) { | ||
message.error('Error occurred while creating feedback. Please try later.'); | ||
} | ||
}; | ||
|
||
return ( | ||
<Form | ||
style={{ margin: '24px 0' }} | ||
onFinish={handleSubmit} | ||
form={form} | ||
layout="vertical" | ||
initialValues={{ studentId }} | ||
> | ||
<Alert | ||
showIcon | ||
type="info" | ||
message={ | ||
<> | ||
<div>This feedback is very important for RS School process.</div> | ||
<div>Please spend 5 minutes to complete it. Thank you!</div> | ||
</> | ||
} | ||
/> | ||
<Alert | ||
style={{ marginTop: 8 }} | ||
showIcon | ||
type="warning" | ||
message={ | ||
<div>If you recommend to "Hire", we will attach the feedback to student's CV and it will be public.</div> | ||
} | ||
/> | ||
<Form.Item name="studentId" label="Student"> | ||
<UserSearch allowClear={false} defaultValues={students} keyField="id" onChange={handleStudentChange} /> | ||
</Form.Item> | ||
<Typography.Title level={5}>Recommended To</Typography.Title> | ||
<Form.Item name="recommendation" rules={[{ required: true, message: 'Required' }]}> | ||
<Radio.Group> | ||
<Radio.Button value={RecommendationEnum.Hire}>Hire</Radio.Button> | ||
<Radio.Button value={RecommendationEnum.NotHire}>Not Hire</Radio.Button> | ||
</Radio.Group> | ||
</Form.Item> | ||
<Form.Item name="recommendationComment" rules={[{ required: true, message: 'Required' }]} label="What was good"> | ||
<TextArea rows={7} allowClear /> | ||
</Form.Item> | ||
<Form.Item name="suggestions" label="What could be improved"> | ||
<TextArea rows={3} allowClear /> | ||
</Form.Item> | ||
<Typography.Title level={5}>English</Typography.Title> | ||
<Form.Item label="Approximate English level" name="englishLevel"> | ||
<Radio.Group> | ||
{englishLevels.map(level => ( | ||
<Radio.Button key={level} value={level}> | ||
{level.toUpperCase()} | ||
</Radio.Button> | ||
))} | ||
</Radio.Group> | ||
</Form.Item> | ||
|
||
<Typography.Title level={5}>Soft Skills</Typography.Title> | ||
<Row wrap={true}> | ||
{softSkills.map(({ id, name }) => ( | ||
<Col key={id} span={12}> | ||
<Form.Item key={id} label={name} name={id}> | ||
<Rate /> | ||
</Form.Item> | ||
</Col> | ||
))} | ||
</Row> | ||
<Button htmlType="submit" type="primary"> | ||
Submit | ||
</Button> | ||
</Form> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { SoftSkillEntryIdEnum, SoftSkillEntryValueEnum } from 'api'; | ||
|
||
export const softSkills: { id: SoftSkillEntryIdEnum; name: string }[] = [ | ||
{ | ||
id: SoftSkillEntryIdEnum.Responsible, | ||
name: 'Responsible', | ||
}, | ||
{ | ||
id: SoftSkillEntryIdEnum.TeamPlayer, | ||
name: 'Good team player', | ||
}, | ||
{ | ||
id: SoftSkillEntryIdEnum.Communicable, | ||
name: 'Communicable', | ||
}, | ||
]; | ||
|
||
export const convertSoftSkillValueToEnum = (value: number | null) => { | ||
switch (value) { | ||
case 1: | ||
return SoftSkillEntryValueEnum.Poor; | ||
case 2: | ||
return SoftSkillEntryValueEnum.Fair; | ||
case 3: | ||
return SoftSkillEntryValueEnum.Good; | ||
case 4: | ||
return SoftSkillEntryValueEnum.Great; | ||
case 5: | ||
return SoftSkillEntryValueEnum.Excellent; | ||
default: | ||
return SoftSkillEntryValueEnum.None; | ||
} | ||
}; | ||
|
||
export const softSkillValues: Record<SoftSkillEntryValueEnum, number> = { | ||
[SoftSkillEntryValueEnum.None]: 0, | ||
[SoftSkillEntryValueEnum.Poor]: 1, | ||
[SoftSkillEntryValueEnum.Fair]: 2, | ||
[SoftSkillEntryValueEnum.Good]: 3, | ||
[SoftSkillEntryValueEnum.Great]: 4, | ||
[SoftSkillEntryValueEnum.Excellent]: 5, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,30 @@ | ||
import { MentorsApi } from 'api'; | ||
import { useMemo } from 'react'; | ||
import { useAsync } from 'react-use'; | ||
import { message } from 'antd'; | ||
import { MentorsApi, MentorStudentDto } from 'api'; | ||
import { useMemo, useState, useEffect, useCallback } from 'react'; | ||
|
||
export function useMentorStudents(mentorId: number | null) { | ||
const service = useMemo(() => new MentorsApi(), []); | ||
|
||
const { value: students, loading } = useAsync(async () => { | ||
const { data = [] } = mentorId ? await service.getMentorStudents(mentorId) : { data: [] }; | ||
return data; | ||
}, []); | ||
const [students, setStudents] = useState<MentorStudentDto[]>([]); | ||
const [loading, setLoading] = useState<boolean>(false); | ||
|
||
return [students, loading] as const; | ||
const fetchStudents = useCallback(async () => { | ||
if (mentorId) { | ||
setLoading(true); | ||
try { | ||
const { data = [] } = await service.getMentorStudents(mentorId); | ||
setStudents(data); | ||
} catch (error) { | ||
message.error('Failed to fetch students'); | ||
} finally { | ||
setLoading(false); | ||
} | ||
} | ||
}, [mentorId, service]); | ||
|
||
useEffect(() => { | ||
fetchStudents(); | ||
}, [fetchStudents]); | ||
|
||
return { students, loading, reload: fetchStudents }; | ||
} |
Oops, something went wrong.