Skip to content

Commit

Permalink
feat: add detailed feedback in cross-check-pairs table (#2258)
Browse files Browse the repository at this point in the history
* feat: add detailed feedback in cross-check-pairs table

* refactor: remove code duplicate

* refactor: create table component

* refactor: prettier

* fix: update imports

* refactor: prettier

* refactor: fixes after code review
  • Loading branch information
valerydluski authored Aug 12, 2023
1 parent 9091140 commit 4d47dd9
Show file tree
Hide file tree
Showing 17 changed files with 500 additions and 444 deletions.
8 changes: 7 additions & 1 deletion client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2547,7 +2547,7 @@ export interface EndorsementDto {
* @type {object}
* @memberof EndorsementDto
*/
'data': object;
'data': object | null;
}
/**
*
Expand Down Expand Up @@ -2998,6 +2998,12 @@ export interface HistoricalScoreDto {
* @memberof HistoricalScoreDto
*/
'dateTime': string;
/**
*
* @type {Array<CrossCheckCriteriaDataDto>}
* @memberof HistoricalScoreDto
*/
'criteria'?: Array<CrossCheckCriteriaDataDto>;
}
/**
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Typography } from 'antd';
import { TaskType } from '../CrossCheckCriteriaForm';
import { CrossCheckCriteriaDataDto } from 'api';

const { Text, Title } = Typography;

type Props = {
criteria: CrossCheckCriteriaDataDto[] | null;
};

export function CrossCheckCriteria({ criteria }: Props) {
if (!criteria?.length) return null;
const penaltyData = criteria.filter(
criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Penalty && criteriaItem.point,
);

return (
<>
{criteria
.filter(criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Subtask)
.map(criteriaItem => (
<div key={criteriaItem.key} style={{ border: '1px solid #F5F5F5', margin: '24px 0', paddingBottom: '14px' }}>
<div
style={{
display: 'block',
fontSize: '14px',
background: '#FAFAFA',
borderBottom: '1px solid #F5F5F5',
padding: '14px 12px',
marginBottom: '14px',
}}
>
<Text>{criteriaItem.text}</Text>
</div>

{criteriaItem.textComment && (
<div style={{ padding: '0 12px', fontSize: '16px' }}>
<Text strong={true}>Comment:</Text>
{criteriaItem.textComment?.split('\n').map((textLine, k) => (
<p key={k} style={{ margin: '0px 0 5px 0' }}>
{textLine}
</p>
))}
</div>
)}
<div style={{ fontSize: '16px', padding: '0 12px' }}>
<Text strong={true}>Points for criteria: {`${criteriaItem.point ?? 0}/${criteriaItem.max}`}</Text>
</div>
</div>
))}
{penaltyData?.length ? (
<div style={{ marginTop: '20px' }}>
<Title level={4}>Penalty</Title>
{penaltyData?.map(criteriaItem => (
<div
key={criteriaItem.key}
style={{
display: 'inline-block',
width: '100%',
backgroundColor: '#fff2f2',
border: '1px #ffb0b0 solid',
padding: '14px 12px',
}}
>
<Text>
{criteriaItem.text} {criteriaItem.point ?? 0}
</Text>
</div>
))}
</div>
) : null}
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Modal, Typography } from 'antd';
import { TaskType } from '../CrossCheckCriteriaForm';
import { Modal } from 'antd';
import { CrossCheckCriteria } from './CrossCheckCriteria';
import { CrossCheckCriteriaDataDto } from 'api';

const { Text, Title } = Typography;

type Props = {
modalInfo: CrossCheckCriteriaDataDto[] | null;
isModalVisible: boolean;
Expand All @@ -19,65 +17,9 @@ export function CrossCheckCriteriaModal({ modalInfo, isModalVisible, showModal }
showModal(false);
};

const penaltyData = modalInfo?.filter(
criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Penalty && criteriaItem.point,
);

return (
<Modal title="Feedback" open={isModalVisible} onOk={handleOk} onCancel={handleCancel} width={1000}>
{modalInfo
?.filter(criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Subtask)
.map(criteriaItem => (
<div key={criteriaItem.key} style={{ border: '1px solid #F5F5F5', margin: '24px 0', paddingBottom: '14px' }}>
<div
style={{
display: 'block',
fontSize: '14px',
background: '#FAFAFA',
borderBottom: '1px solid #F5F5F5',
padding: '14px 12px',
marginBottom: '14px',
}}
>
<Text>{criteriaItem.text}</Text>
</div>

{criteriaItem.textComment && (
<div style={{ padding: '0 12px', fontSize: '16px' }}>
<Text strong={true}>Comment:</Text>
{criteriaItem.textComment?.split('\n').map((textLine, k) => (
<p key={k} style={{ margin: '0px 0 5px 0' }}>
{textLine}
</p>
))}
</div>
)}
<div style={{ fontSize: '16px', padding: '0 12px' }}>
<Text strong={true}>Points for criteria: {`${criteriaItem.point ?? 0}/${criteriaItem.max}`}</Text>
</div>
</div>
))}
{penaltyData?.length ? (
<div style={{ marginTop: '20px' }}>
<Title level={4}>Penalty</Title>
{penaltyData?.map(criteriaItem => (
<div
key={criteriaItem.key}
style={{
display: 'inline-block',
width: '100%',
backgroundColor: '#fff2f2',
border: '1px #ffb0b0 solid',
padding: '14px 12px',
}}
>
<Text>
{criteriaItem.text} {criteriaItem.point ?? 0}
</Text>
</div>
))}
</div>
) : null}
<CrossCheckCriteria criteria={modalInfo} />
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Table, TablePaginationConfig } from 'antd';
import { CrossCheckPairDto } from 'api';
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
import {
CustomColumnType,
fields,
getCrossCheckPairsColumns,
} from 'modules/CrossCheckPairs/data/getCrossCheckPairsColumns';
import css from 'styled-jsx/css';

export type Filters = Omit<typeof fields, 'score' | 'submittedDate' | 'reviewedDate'>;

interface CustomSorterResult<RecordType> extends SorterResult<RecordType> {
column?: CustomColumnType<RecordType>;
}

export type Sorter<RecordType> = CustomSorterResult<RecordType> | CustomSorterResult<RecordType>[];

type CrossCheckTableProps = {
loaded: boolean;
crossCheckPairs: CrossCheckPairDto[];
pagination: TablePaginationConfig;
onChange: (
pagination: TablePaginationConfig,
filters: Record<keyof Filters, FilterValue | null>,
sorter: Sorter<CrossCheckPairDto>,
) => void;
viewComment: (value: CrossCheckPairDto) => void;
};

export const CrossCheckPairsTable = ({
loaded,
crossCheckPairs,
pagination,
onChange,
viewComment,
}: CrossCheckTableProps) => {
if (!loaded) return null;

// where 800 is approximate sum of basic columns (GitHub, Name, etc.)
const tableWidth = 800;
return (
<>
<Table<CrossCheckPairDto>
className="table-score"
showHeader
scroll={{ x: tableWidth, y: 'calc(100vh - 250px)' }}
pagination={pagination}
dataSource={crossCheckPairs}
size="small"
rowClassName={'cross-check-table-row'}
onChange={onChange}
key="id"
columns={getCrossCheckPairsColumns(viewComment)}
/>
<style jsx>{styles}</style>
</>
);
};

const styles = css`
:global(.cross-check-table-row, .table-score td, .table-score th) {
padding: 0 5px !important;
font-size: 11px;
}
`;
114 changes: 114 additions & 0 deletions client/src/modules/CrossCheckPairs/data/getCrossCheckPairsColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { CrossCheckPairDto } from 'api';
import { ColumnType } from 'antd/lib/table/interface';
import { omit } from 'lodash';
import { dateTimeRenderer, getColumnSearchProps } from 'components/Table';
import { GithubAvatar } from 'components/GithubAvatar';
import { Button } from 'antd';

export const fields = {
task: 'task',
checker: 'checker',
student: 'student',
url: 'url',
score: 'score',
submittedDate: 'submittedDate',
reviewedDate: 'reviewedDate',
};

export interface CustomColumnType<RecordType> extends ColumnType<RecordType> {
sorterField?: string;
}

const renderGithubLink = (value: string) =>
value ? (
<div>
<GithubAvatar githubId={value} size={24} />
&nbsp;
<a target="_blank" rel="noopener noreferrer" href={`https://github.com/${value}`}>
{value}
</a>
</div>
) : null;

export const getCrossCheckPairsColumns = (
viewComment: (value: CrossCheckPairDto) => void,
): CustomColumnType<CrossCheckPairDto>[] => [
{
title: 'Task',
fixed: 'left',
dataIndex: ['task', 'name'],
key: fields.task,
width: 100,
sorter: true,
sorterField: 'task',
...omit(getColumnSearchProps(['task', 'name']), 'onFilter'),
},
{
title: 'Checker',
fixed: 'left',
key: fields.checker,
dataIndex: ['checker', 'githubId'],
sorter: true,
sorterField: 'checker',
width: 150,
render: renderGithubLink,
...omit(getColumnSearchProps(['checkerStudent', 'githubId']), 'onFilter'),
},
{
title: 'Student',
key: fields.student,
dataIndex: ['student', 'githubId'],
sorter: true,
sorterField: 'student',
width: 150,
render: renderGithubLink,
...omit(getColumnSearchProps(['student', 'githubId']), 'onFilter'),
},
{
title: 'Url',
dataIndex: 'url',
key: fields.url,
width: 150,
sorter: true,
sorterField: 'url',
...getColumnSearchProps('url'),
},
{
title: 'Score',
dataIndex: 'score',
key: fields.score,
width: 80,
sorter: true,
sorterField: 'score',
render: value => <>{value ?? '(Empty)'}</>,
},
{
title: 'Submitted Date',
dataIndex: 'submittedDate',
key: fields.submittedDate,
width: 80,
sorter: true,
sorterField: 'submittedDate',
render: dateTimeRenderer,
},
{
title: 'Reviewed Date',
dataIndex: 'reviewedDate',
key: fields.reviewedDate,
width: 80,
sorter: true,
sorterField: 'reviewedDate',
render: (_, record) => dateTimeRenderer(record.reviewedDate),
},
{
title: 'Comment',
dataIndex: 'comment',
key: 'comment',
width: 60,
render: (_, record) => (
<Button disabled={!record.historicalScores} onClick={() => viewComment(record)} type="link" size="small">
Show
</Button>
),
},
];
Loading

0 comments on commit 4d47dd9

Please sign in to comment.