Skip to content

Commit

Permalink
feat: students details section (#2506)
Browse files Browse the repository at this point in the history
* fix: MentorInfo component to render filledContacts correctly

* feat: add student detail section

* fix: update layout course card

* fix: update column width
  • Loading branch information
valerydluski authored Jul 10, 2024
1 parent 0d0b8f9 commit 1f4be2d
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 24 deletions.
84 changes: 84 additions & 0 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7039,6 +7039,48 @@ export interface UserStudentCourseDto {
* @memberof UserStudentCourseDto
*/
'hasCertificate': boolean;
/**
*
* @type {boolean}
* @memberof UserStudentCourseDto
*/
'completed': boolean;
/**
*
* @type {boolean}
* @memberof UserStudentCourseDto
*/
'studentIsExpelled': boolean;
/**
*
* @type {string}
* @memberof UserStudentCourseDto
*/
'certificateId': string;
/**
*
* @type {string}
* @memberof UserStudentCourseDto
*/
'mentorGithubId': string;
/**
*
* @type {string}
* @memberof UserStudentCourseDto
*/
'mentorFullName': string;
/**
*
* @type {number}
* @memberof UserStudentCourseDto
*/
'totalScore': number;
/**
*
* @type {number}
* @memberof UserStudentCourseDto
*/
'rank': number;
}
/**
*
Expand Down Expand Up @@ -7076,6 +7118,42 @@ export interface UserStudentDto {
* @memberof UserStudentDto
*/
'city': object;
/**
* User email
* @type {string}
* @memberof UserStudentDto
*/
'contactsEmail': string;
/**
* User telegram
* @type {string}
* @memberof UserStudentDto
*/
'contactsTelegram': string;
/**
* User linkedIn
* @type {string}
* @memberof UserStudentDto
*/
'contactsLinkedIn': string;
/**
* User skype
* @type {string}
* @memberof UserStudentDto
*/
'contactsSkype': string;
/**
* User phone
* @type {string}
* @memberof UserStudentDto
*/
'contactsPhone': string;
/**
* User discord
* @type {Discord}
* @memberof UserStudentDto
*/
'discord': Discord;
/**
* User on going courses
* @type {Array<UserStudentCourseDto>}
Expand All @@ -7088,6 +7166,12 @@ export interface UserStudentDto {
* @memberof UserStudentDto
*/
'previousCourses': Array<UserStudentCourseDto>;
/**
* User languages
* @type {Array<string>}
* @memberof UserStudentDto
*/
'languages': Array<string>;
}
/**
*
Expand Down
8 changes: 7 additions & 1 deletion client/src/modules/Students/Pages/Students.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TablePaginationConfig, message } from 'antd';
import { Drawer, TablePaginationConfig, message } from 'antd';
import { FilterValue } from 'antd/es/table/interface';
import { StudentsApi, UserStudentDto } from 'api';
import { IPaginationInfo } from 'common/types/pagination';
Expand All @@ -9,6 +9,7 @@ import { useAsync } from 'react-use';
import StudentsTable from '../components/StudentsTable';
import { ColumnKey } from '../components/StudentsTable/renderers';
import { PageProps } from './getServerSideProps';
import { StudentInfo } from '../components/StudentInfo';

const studentsApi = new StudentsApi();

Expand All @@ -22,6 +23,7 @@ export const Students = ({ courses }: PageProps) => {
content: [],
pagination: { current: 1, pageSize: 20 },
});
const [activeStudent, setActiveStudent] = useState<UserStudentDto | null>(null);

const [loading, withLoading] = useLoading(false);

Expand Down Expand Up @@ -55,7 +57,11 @@ export const Students = ({ courses }: PageProps) => {
content={students.content}
pagination={students.pagination}
courses={courses}
setActiveStudent={setActiveStudent}
/>
<Drawer mask={false} title="Student Details" onClose={() => setActiveStudent(null)} open={!!activeStudent}>
{activeStudent && <StudentInfo student={activeStudent} />}
</Drawer>
</AdminPageLayout>
);
};
38 changes: 38 additions & 0 deletions client/src/modules/Students/components/CourseItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Flex, List, Space, Typography } from 'antd';
import { SafetyCertificateTwoTone } from '@ant-design/icons';
import { UserStudentCourseDto } from 'api';

const { Text, Paragraph, Link } = Typography;

type Props = {
course: UserStudentCourseDto;
};

export const CourseItem = ({ course }: Props) => {
const { name, certificateId, mentorGithubId, mentorFullName, rank, totalScore } = course;

return (
<List.Item>
<Flex vertical gap={4}>
<Text strong>{name}</Text>
{certificateId && (
<Paragraph>
<SafetyCertificateTwoTone twoToneColor="#52c41a" />{' '}
<Link target="__blank" href={`/certificate/${certificateId}`}>
Certificate
</Link>
</Paragraph>
)}
{mentorGithubId && (
<Paragraph>
Mentor: <Link href={`/profile?githubId=${mentorGithubId}`}>{mentorFullName}</Link>
</Paragraph>
)}
<Space wrap>
{rank && <Paragraph>Position: {rank}</Paragraph>}
<Paragraph>Score: {totalScore}</Paragraph>
</Space>
</Flex>
</List.Item>
);
};
94 changes: 94 additions & 0 deletions client/src/modules/Students/components/StudentInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Avatar, Col, Collapse, List, Row, Space, Typography } from 'antd';
import GithubFilled from '@ant-design/icons/GithubFilled';
import MailOutlined from '@ant-design/icons/MailOutlined';
import LinkedinOutlined from '@ant-design/icons/LinkedinOutlined';
import SkypeOutlined from '@ant-design/icons/SkypeOutlined';
import PhoneOutlined from '@ant-design/icons/PhoneOutlined';
import SendOutlined from '@ant-design/icons/SendOutlined';
import { DiscordOutlined } from 'components/Icons/DiscordOutlined';
import { GithubAvatar } from 'components/GithubAvatar';
import { UserStudentDto } from 'api';
import { CourseItem } from '../CourseItem';

type Props = {
student: UserStudentDto;
};

const { Panel } = Collapse;

const { Text } = Typography;

export function StudentInfo(props: Props) {
const { student } = props;
const { githubId, fullName } = student;
const hasName = fullName && fullName !== '(Empty)';
const location = [student.city, student.country].filter(Boolean).join(', ');

const UserContacts = (student: UserStudentDto) => {
const contacts = [
{ type: 'Email', value: student.contactsEmail, icon: <MailOutlined /> },
{ type: 'Telegram', value: student.contactsTelegram, icon: <SendOutlined rotate={-45} /> },
{ type: 'LinkedIn', value: student.contactsLinkedIn, icon: <LinkedinOutlined /> },
{ type: 'Skype', value: student.contactsSkype, icon: <SkypeOutlined /> },
{ type: 'Phone', value: student.contactsPhone, icon: <PhoneOutlined /> },
{ type: 'Discord', value: student.discord?.username, icon: <DiscordOutlined /> },
];
return contacts.filter(contact => contact.value);
};

return (
<Space direction="vertical" style={{ width: '100%' }}>
<Row align="middle" gutter={24}>
<Col>
<GithubAvatar githubId={githubId} size={48} />
</Col>
<Col>
{hasName && (
<Row>
<Typography.Link target="_blank" href={`/profile?githubId=${githubId}`} strong>
{fullName}
</Typography.Link>
</Row>
)}
<Row>
<Typography.Link target="_blank" href={`https://github.com/${githubId}`}>
<GithubFilled /> {githubId}
</Typography.Link>
</Row>
</Col>
</Row>
<Row>
<Col span={24}>
<Row>
<Text type="secondary">Location</Text>
</Row>
<Row>
<Text>{location}</Text>
</Row>
</Col>
</Row>
<Collapse defaultActiveKey={['courses']}>
<Panel header="Contacts" key="contacts">
<List
itemLayout="horizontal"
dataSource={UserContacts(student)}
renderItem={item => (
<List.Item>
<List.Item.Meta avatar={<Avatar icon={item.icon} />} title={item.type} description={item.value} />
</List.Item>
)}
/>
</Panel>
<Panel header="Courses" key="courses">
<List
itemLayout="horizontal"
dataSource={[...student.previousCourses, ...student.onGoingCourses].sort(course =>
course.hasCertificate ? -1 : 1,
)}
renderItem={course => <CourseItem course={course} />}
/>
</Panel>
</Collapse>
</Space>
);
}
15 changes: 14 additions & 1 deletion client/src/modules/Students/components/StudentsTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ type Props = {
handleChange: TableProps<UserStudentDto>['onChange'];
loading: boolean;
courses: CourseDto[];
setActiveStudent: (student: UserStudentDto | null) => void;
};

export default function StudentsTable({ content, pagination, handleChange, loading, courses }: Props) {
export default function StudentsTable({
content,
pagination,
handleChange,
loading,
courses,
setActiveStudent,
}: Props) {
return (
<Table<UserStudentDto>
showHeader
Expand All @@ -20,6 +28,11 @@ export default function StudentsTable({ content, pagination, handleChange, loadi
onChange={handleChange}
rowKey="id"
pagination={pagination}
onRow={record => {
return {
onClick: () => setActiveStudent(record),
};
}}
loading={loading}
bordered
/**
Expand Down
26 changes: 24 additions & 2 deletions client/src/modules/Students/components/StudentsTable/renderers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Space, Tag, Tooltip } from 'antd';
import { Flex, Space, Tag, Tooltip } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { CourseDto, UserStudentCourseDto, UserStudentDto } from 'api';
import { GithubUserLink } from 'components/GithubUserLink';
Expand All @@ -10,6 +10,7 @@ export enum ColumnKey {
PreviousCourses = 'previousCourses',
Country = 'country',
City = 'city',
Languages = 'languages',
}

enum ColumnName {
Expand All @@ -18,6 +19,7 @@ enum ColumnName {
PreviousCourses = 'Previous Courses',
Country = 'Country',
City = 'City',
Languages = 'Languages',
}

const getSearchProps = (key: string) => ({
Expand Down Expand Up @@ -63,33 +65,53 @@ export const getColumns = (courses: CourseDto[]): ColumnsType<UserStudentDto> =>
key: ColumnKey.Student,
title: ColumnName.Student,
dataIndex: ColumnKey.Student,
width: 200,
width: 225,
render: (_v, record) => <GithubUserLink value={record.githubId} fullName={record.fullName} />,
...getSearchProps(ColumnKey.Student),
},
{
title: ColumnName.OnGoingCourses,
key: ColumnKey.OnGoingCourses,
dataIndex: ColumnKey.OnGoingCourses,
width: 400,
render: coursesRenderer,
filters: courses.filter(course => !course.completed).map(course => ({ text: course.alias, value: course.id })),
filterSearch: true,
},
{
title: ColumnName.PreviousCourses,
key: ColumnKey.PreviousCourses,
dataIndex: ColumnKey.PreviousCourses,
render: coursesRenderer,
width: 400,
filters: courses.filter(course => course.completed).map(course => ({ text: course.alias, value: course.id })),
filterSearch: true,
},
{
title: ColumnName.Country,
key: ColumnKey.Country,
dataIndex: ColumnKey.Country,
width: 200,
...getSearchProps(ColumnKey.Country),
},
{
title: ColumnName.City,
key: ColumnKey.City,
dataIndex: ColumnKey.City,
width: 200,
...getSearchProps(ColumnKey.City),
},
{
title: ColumnName.Languages,
dataIndex: ColumnKey.Languages,
key: ColumnKey.Languages,
width: 150,
render: (languages: string[]) => (
<Flex wrap="wrap">
{languages.map(language => (
<Tag key={language}>{language}</Tag>
))}
</Flex>
),
},
];
6 changes: 5 additions & 1 deletion nestjs/src/auth/auth-user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ export class AuthUser {
this.isAdmin = admin;
this.isHirer = hirer;
this.githubId = user.githubId;
this.appRoles = [this.isAdmin ? Role.Admin : Role.User, this.isHirer ? Role.Hirer : null].filter(Boolean);
this.appRoles = [this.isAdmin ? Role.Admin : Role.User];
//fix openapi generator issue
if (this.isHirer) {
this.appRoles.push(Role.Hirer);
}
this.roles = roles;
this.courses = coursesInfo;
return this;
Expand Down
Loading

0 comments on commit 1f4be2d

Please sign in to comment.