Skip to content

Commit

Permalink
feat: add criteria settings set based on task type (#2338)
Browse files Browse the repository at this point in the history
* fix: types, task settings visibility, performance issue

* test: use proper props

* refactor: rename types, props, functions

* refactor: rearrange form items

* refactor: extract static constant

* fix: add tasks into github settings

* refactor: use useModalForm hook
  • Loading branch information
I-vasilich-I authored Nov 16, 2023
1 parent dbcca67 commit 82ab2db
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 131 deletions.
4 changes: 3 additions & 1 deletion client/src/components/Forms/ModalForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Form, Modal, Spin } from 'antd';
import { ButtonProps, Form, Modal, Spin } from 'antd';
import { FormInstance } from 'antd/es/form/Form';
import * as React from 'react';

Expand All @@ -13,6 +13,7 @@ type Props = {
loading?: boolean;
okText?: string;
form?: FormInstance;
okButtonProps?: ButtonProps;
};

export function ModalForm(props: Props) {
Expand All @@ -38,6 +39,7 @@ export function ModalForm(props: Props) {
}
props.submit(values);
}}
okButtonProps={{ disabled: props.loading, ...props.okButtonProps }}
onCancel={e => {
props.cancel(e);
form.resetFields();
Expand Down
1 change: 1 addition & 0 deletions client/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { useModalForm } from './useModal/useModalForm';
export type { ModalFormMode } from './useModal/useModalForm';
4 changes: 3 additions & 1 deletion client/src/hooks/useModal/useModalForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useState } from 'react';

export type ModalFormMode = 'create' | 'edit';

export const useModalForm = <T,>() => {
const [mode, setMode] = useState<'create' | 'edit'>('create');
const [mode, setMode] = useState<ModalFormMode>('create');
const [open, setOpen] = useState(false);
const [formData, setFormData] = useState<T>();

Expand Down
32 changes: 18 additions & 14 deletions client/src/modules/Tasks/components/TaskModal/TaskModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { generateTasksData } from 'modules/Tasks/utils/test-utils';
import { FormValues } from 'modules/Tasks/types';
import { ERROR_MESSAGES, LABELS, MODAL_TITLES, PLACEHOLDERS, TASK_SETTINGS_HEADERS } from 'modules/Tasks/constants';
import { ModalProps, TaskModal } from './TaskModal';
import { fireEvent, render, screen } from '@testing-library/react';
import { ModalData } from 'modules/Tasks/types';
import { ERROR_MESSAGES, LABELS, PLACEHOLDERS, TASK_SETTINGS_HEADERS } from 'modules/Tasks/constants';

const mockData = generateData();

describe('TaskModal', () => {
test('should render modal', () => {
test('should render modal with proper title', () => {
render(<TaskModal {...mockData} />);

const modal = screen.getByRole('dialog');
expect(modal).toBeInTheDocument();

const title = screen.getByText(MODAL_TITLES.edit);
expect(title).toBeInTheDocument();
});

test('should render labels', () => {
Expand Down Expand Up @@ -127,29 +130,30 @@ describe('TaskModal', () => {

function generateData(isEmpty = false): ModalProps {
const tasks = generateTasksData();
const modalData: ModalData = {
const formData: FormValues = {
...tasks[0],
attributes: undefined,
discipline: tasks[0].discipline.id,
};

if (isEmpty) {
modalData.name = undefined;
modalData.type = undefined;
modalData.discipline = undefined;
modalData.descriptionUrl = undefined;
modalData.tags = undefined;
modalData.skills = undefined;
formData.name = undefined;
formData.type = undefined;
formData.discipline = undefined;
formData.descriptionUrl = undefined;
formData.tags = undefined;
formData.skills = undefined;
}

return {
tasks,
dataCriteria: [],
modalData,
formData,
modalLoading: false,
disciplines: [],
mode: isEmpty ? 'create' : 'edit',
setDataCriteria: jest.fn(),
handleModalSubmit: jest.fn(),
setModalData: jest.fn(),
setModalValues: jest.fn(),
toggleModal: jest.fn(),
};
}
117 changes: 62 additions & 55 deletions client/src/modules/Tasks/components/TaskModal/TaskModal.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
import { Row, Col, Form, Input, Select, Card, Space, Tag, Empty, Typography } from 'antd';
import { useMemo } from 'react';
import { union } from 'lodash';
import union from 'lodash/union';
import { TaskDto, CriteriaDto, DisciplineDto } from 'api';
import { ModalForm } from 'components/Forms';
import { stringSorter } from 'components/Table';
import { SKILLS } from 'data/skills';
import { TASK_TYPES } from 'data/taskTypes';
import { ModalFormMode } from 'hooks';
import { TaskSettings } from 'modules/Tasks/components';
import { ERROR_MESSAGES, LABELS, PLACEHOLDERS } from 'modules/Tasks/constants';
import { ModalData } from 'modules/Tasks/types';
import { ERROR_MESSAGES, LABELS, MODAL_TITLES, PLACEHOLDERS } from 'modules/Tasks/constants';
import { FormValues } from 'modules/Tasks/types';
import { urlPattern } from 'services/validators';

const { Text } = Typography;
const { TextArea } = Input;

const taskTypes = TASK_TYPES.sort(stringSorter('name')).map(({ id, name }) => ({ value: id, label: name }));

export type ModalProps = {
tasks: TaskDto[];
modalData: ModalData;
formData: FormValues | undefined;
dataCriteria: CriteriaDto[];
modalLoading: boolean;
disciplines: DisciplineDto[];
mode: ModalFormMode;
toggleModal: (data?: FormValues) => void;
setDataCriteria: (criteria: CriteriaDto[]) => void;
handleModalSubmit: (values: any) => Promise<void>;
setModalData: (data: ModalData) => void;
setModalValues: (value: any) => void;
handleModalSubmit: (values: FormValues) => Promise<void>;
};

export function TaskModal({
tasks,
dataCriteria,
modalData,
modalLoading,
disciplines,
mode,
formData,
toggleModal,
setDataCriteria,
handleModalSubmit,
setModalData,
setModalValues,
}: ModalProps) {
const [form] = Form.useForm<FormValues>();
const typeField = Form.useWatch('type', form);

const allTags = useMemo(() => union(...tasks.map(task => task.tags || [])), [tasks]);
const allSkills = useMemo(
() =>
Expand All @@ -47,35 +55,54 @@ export function TaskModal({
[tasks],
);

const handleTypeChange = () => {
// reset settings
form.setFieldsValue({
githubPrRequired: undefined,
githubRepoName: undefined,
sourceGithubRepoUrl: undefined,
attributes: undefined,
});
setDataCriteria([]);
};

return (
<ModalForm
data={modalData}
title="Task"
data={formData ?? {}}
form={form}
title={MODAL_TITLES[mode]}
submit={handleModalSubmit}
cancel={() => {
setModalData(null);
toggleModal();
setDataCriteria([]);
}}
onChange={setModalValues}
getInitialValues={getInitialValues}
loading={modalLoading}
>
<Form.Item name="name" label={LABELS.name} rules={[{ required: true, message: ERROR_MESSAGES.name }]}>
<Input placeholder={PLACEHOLDERS.name} />
</Form.Item>
<Form.Item
name="descriptionUrl"
label={LABELS.descriptionUrl}
rules={[
{
required: true,
message: ERROR_MESSAGES.descriptionUrl,
},
{
message: ERROR_MESSAGES.validUrl,
pattern: urlPattern,
},
]}
>
<Input placeholder={PLACEHOLDERS.descriptionUrl} />
</Form.Item>
<Row gutter={24}>
<Col span={12}>
<Form.Item name="name" label={LABELS.name} rules={[{ required: true, message: ERROR_MESSAGES.name }]}>
<Input placeholder={PLACEHOLDERS.name} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="type" label={LABELS.taskType} rules={[{ required: true, message: ERROR_MESSAGES.taskType }]}>
<Select
placeholder={PLACEHOLDERS.taskType}
options={TASK_TYPES.map(({ id, name }) => ({ value: id, label: name }))}
/>
<Select placeholder={PLACEHOLDERS.taskType} options={taskTypes} onChange={handleTypeChange} />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
name="discipline"
Expand All @@ -88,6 +115,8 @@ export function TaskModal({
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item name="tags" label={LABELS.tags}>
<Select
Expand All @@ -97,28 +126,7 @@ export function TaskModal({
/>
</Form.Item>
</Col>
</Row>
<Form.Item
name="descriptionUrl"
label={LABELS.descriptionUrl}
rules={[
{
required: true,
message: ERROR_MESSAGES.descriptionUrl,
},
{
message: ERROR_MESSAGES.validUrl,
pattern: urlPattern,
},
]}
>
<Input placeholder={PLACEHOLDERS.descriptionUrl} />
</Form.Item>
<Form.Item name="description" label={LABELS.summary}>
<Input placeholder={PLACEHOLDERS.summary} />
</Form.Item>
<Row gutter={24}>
<Col span={24}>
<Col span={12}>
<Form.Item name="skills" label={LABELS.skills}>
<Select
mode="tags"
Expand All @@ -128,14 +136,17 @@ export function TaskModal({
</Form.Item>
</Col>
</Row>
<Form.Item name="description" label={LABELS.summary}>
<TextArea placeholder={PLACEHOLDERS.summary} maxLength={100} showCount />
</Form.Item>
<Row gutter={24}>
<Col span={24}>
<Space direction="vertical" size={8} style={{ width: '100%', marginBottom: 24 }}>
<Text>{LABELS.usedInCourses}</Text>
<Card bodyStyle={{ padding: 8 }}>
{modalData?.courses?.length ? (
{formData?.courses?.length ? (
<Space size={[0, 8]} wrap>
{modalData.courses.map(({ name, isActive }) => (
{formData.courses.map(({ name, isActive }) => (
<Tag key={name} color={isActive ? 'blue' : ''}>
{name}
</Tag>
Expand All @@ -148,11 +159,7 @@ export function TaskModal({
</Space>
</Col>
</Row>
<TaskSettings dataCriteria={dataCriteria} setDataCriteria={setDataCriteria} />
<TaskSettings dataCriteria={dataCriteria} setDataCriteria={setDataCriteria} taskType={typeField} />
</ModalForm>
);
}

function getInitialValues(modalData: Partial<TaskDto>) {
return { ...modalData, discipline: modalData.discipline?.id };
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { render, screen } from '@testing-library/react';
import { TaskSettings } from './TaskSettings';
import { Form } from 'antd';
import { TASK_SETTINGS_HEADERS } from 'modules/Tasks/constants';
import { CriteriaDto } from 'api';
import { TASK_SETTINGS_HEADERS } from 'modules/Tasks/constants';
import { TaskSettings } from './TaskSettings';

const renderTaskSettings = (dataCriteria: CriteriaDto[] = [], setDataCriteria = jest.fn()) => {
render(
<Form>
<TaskSettings dataCriteria={dataCriteria} setDataCriteria={setDataCriteria} />
<TaskSettings dataCriteria={dataCriteria} setDataCriteria={setDataCriteria} taskType={undefined} />
</Form>,
);
};
Expand Down
Loading

0 comments on commit 82ab2db

Please sign in to comment.