diff --git a/apps/schools/domains/circle/components/circleList/index.tsx b/apps/schools/domains/circle/components/circleList/index.tsx index 5962b543..ac89db71 100644 --- a/apps/schools/domains/circle/components/circleList/index.tsx +++ b/apps/schools/domains/circle/components/circleList/index.tsx @@ -12,7 +12,7 @@ import { useGetAllCirclesQuery } from '@domains/organization/redux/organizationA import EmptyWrapper from '@domains/common/components/containers/EmptyWrapper' import { mapReturnedData } from '@domains/common/redux/utils' import { HighlightText } from '@domains/common/components/table/forming' -import { getVarsForAddressColumn } from '@domains/student/components/studentList/utils' +import { getVarsForAddressColumn } from '@domains/common/utils/geo' export function CircleList() { const [searchRequestText, setSearchRequestText] = useState('') diff --git a/apps/schools/domains/circle/components/createCircleForm/constants.ts b/apps/schools/domains/circle/components/createCircleForm/constants.ts new file mode 100644 index 00000000..6c8d24b7 --- /dev/null +++ b/apps/schools/domains/circle/components/createCircleForm/constants.ts @@ -0,0 +1,3 @@ +export const CIRCLE_NAME = 'circle_name' +export const CIRCLE_ADDRESS = 'circle_address' +export const ADDRESS_ROOM = 'address_room' diff --git a/apps/schools/domains/circle/components/createCircleForm/hooks.ts b/apps/schools/domains/circle/components/createCircleForm/hooks.ts new file mode 100644 index 00000000..1508ef15 --- /dev/null +++ b/apps/schools/domains/circle/components/createCircleForm/hooks.ts @@ -0,0 +1,30 @@ +import { useMemo } from 'react' +import { PleaseInputAddressMsg, PleaseInputCircleNameMsg } from '@domains/user/components/auth/constants/message' +import { ValidatorsMap } from '@domains/common/redux/interfaces' +import { getGreaterValidator } from '@domains/common/utils/validators' + +export const useCreateCircleFormValidators = () => { + return useMemo(() => { + return { + name: [ + { + required: true, + message: PleaseInputCircleNameMsg, + whitespace: true, + type: 'string', + }, + getGreaterValidator(200), + ], + address: [ + { + required: true, + message: PleaseInputAddressMsg, + whitespace: true, + type: 'string', + }, + getGreaterValidator(200), + ], + room: [getGreaterValidator(55)], + } + }, [this]) +} diff --git a/apps/schools/domains/circle/components/createCircleForm/index.tsx b/apps/schools/domains/circle/components/createCircleForm/index.tsx new file mode 100644 index 00000000..6e8154c5 --- /dev/null +++ b/apps/schools/domains/circle/components/createCircleForm/index.tsx @@ -0,0 +1,143 @@ +import { Form, Typography, Input as AntdInput, Row, Spin } from 'antd' +import React, { useState } from 'react' +import { Input } from '@domains/common/components/input' +import styles from './styles/styles.module.scss' +import { Button } from '@domains/common/components/button' +import { useCreateCircleFormValidators } from './hooks' +import { useGetAllCirclesQuery } from '@domains/organization/redux/organizationApi' +import { useOrganization } from '@domains/organization/providers/organizationProvider' +import { WithTooltip } from '@domains/common/components/tooltip/withTooltip' +import { TOOLTIP_MARGIN } from './styles/styles' +import { isValidFormCheck } from '@domains/common/utils/form' +import { CIRCLE_NAME, CIRCLE_ADDRESS, ADDRESS_ROOM } from './constants' +import classnames from 'classnames' +import { AimOutlined } from '@ant-design/icons' +import { Select } from '@domains/common/components/select' +import { handleSubmitForm } from '../../handlers/circle' +import { useCreateCircleMutation } from '../../redux/circleApi' +import { getVarsForAddressColumn } from '@domains/common/utils/geo' + +export const CreateCircleForm = () => { + const validators = useCreateCircleFormValidators() + const { organization, organizationId } = useOrganization() + const [form] = Form.useForm() + const [isFormValid, setIsFormValid] = useState(false) + const [mutation] = useCreateCircleMutation() + const circlesData = useGetAllCirclesQuery({ + organization_id: organization.id, + }) + const circlesAddresses = Array.from( + new Set(circlesData?.data?.results.map((x) => getVarsForAddressColumn(x.address)[0])), + ) + + const validationCheck = () => { + setIsFormValid(isValidFormCheck(form, [CIRCLE_NAME])) + } + + return ( + +
+
{ + handleSubmitForm(organizationId, form, mutation).then((isSucceed) => { + if (isSucceed) window.location.href = '/circle' + }) + }} + layout='vertical' + > + Добавление кружка + + + * Название + + } + name={CIRCLE_NAME} + className={styles.label} + rules={validators.name} + > + + + + + + {!circlesData.isLoading ? ( + <> + + + * Адрес + + } + name={CIRCLE_ADDRESS} + initialValue={circlesAddresses[0]} + className={classnames(styles.label, styles.address)} + rules={validators.address} + > + + + + + + + ) : ( + + )} + + + + + +
+
+
+ Приложение родителей + + Родители смогут увидеть ваш кружок с помощью карты и узнать информацию о нём! + +
+
+ ) +} diff --git a/apps/schools/domains/circle/components/createCircleForm/styles/styles.module.scss b/apps/schools/domains/circle/components/createCircleForm/styles/styles.module.scss new file mode 100644 index 00000000..7c7c2799 --- /dev/null +++ b/apps/schools/domains/circle/components/createCircleForm/styles/styles.module.scss @@ -0,0 +1,89 @@ +@import '../../../../common/components/styles/abstracts/colors'; + +.mainRow { + height: 100px; + justify-content: space-between; + + .formContainer { + width: 700px; + + .table { + max-width: 550px; + + .requiredMark { + color: $color-required-mark; + } + + .label { + font-family: 'Roboto', sans-serif; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; + } + + .complexInputContainer { + width: 125%; + + .complexInput { + width: 76%; + + .address { + width: 65%; + border-radius: 0; + + .input { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + + .room { + width: 35%; + + .input { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + } + + .mapButton { + width: 24%; + margin-top: 38px; + color: $main-blue-color; + padding-left: 1%; + } + } + + + .button { + width: 30%; + text-align: center; + font-family: 'Roboto', sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + } + } + } + + .mobileApp { + padding-left: 60px; + padding-right: 60px; + padding-top: 30px; + margin-top: 20px; + height: 814px; + width: 505px; + border-radius: 12px; + background: linear-gradient(180deg, rgba(24, 144, 255, 0.3) 0%, rgba(245, 245, 245, 0.00) 100%); + + .text{ + font-size: 24px; + font-style: normal; + font-weight: 600; + line-height: 130%; + } + } +} \ No newline at end of file diff --git a/apps/schools/domains/circle/components/createCircleForm/styles/styles.ts b/apps/schools/domains/circle/components/createCircleForm/styles/styles.ts new file mode 100644 index 00000000..77493475 --- /dev/null +++ b/apps/schools/domains/circle/components/createCircleForm/styles/styles.ts @@ -0,0 +1 @@ +export const TOOLTIP_MARGIN = '47px' diff --git a/apps/schools/domains/circle/handlers/circle.ts b/apps/schools/domains/circle/handlers/circle.ts new file mode 100644 index 00000000..ea85d4dd --- /dev/null +++ b/apps/schools/domains/circle/handlers/circle.ts @@ -0,0 +1,27 @@ +import { FormInstance, message } from 'antd' +import { LoadingRequestMsg, SuccessCreateCircleMsg } from '@domains/user/components/auth/constants/message' +import { removeEmpty } from '@domains/common/utils/form' +import { CIRCLE_NAME, CIRCLE_ADDRESS, ADDRESS_ROOM } from '../components/createCircleForm/constants' +import { withLoadingMessage } from '@domains/common/utils/loading' +import { ADDRESS_SEPARATOR } from '@domains/common/utils/geo' + +export async function handleSubmitForm(organizationId: string, formComponent: FormInstance, mutation: any) { + const response = await withLoadingMessage( + LoadingRequestMsg, + mutation, + removeEmpty({ + organization: organizationId, + name: formComponent.getFieldValue(CIRCLE_NAME), + address: `${formComponent.getFieldValue(CIRCLE_ADDRESS)}${ADDRESS_SEPARATOR}${formComponent.getFieldValue( + ADDRESS_ROOM, + )}`, + }), + ) + + if ('data' in response) { + message.success(SuccessCreateCircleMsg) + return true + } + + return false +} diff --git a/apps/schools/domains/common/components/Logo/styles/styles.module.scss b/apps/schools/domains/common/components/Logo/styles/styles.module.scss index cd63e59f..c6c39687 100644 --- a/apps/schools/domains/common/components/Logo/styles/styles.module.scss +++ b/apps/schools/domains/common/components/Logo/styles/styles.module.scss @@ -7,8 +7,8 @@ } .logoText { - color: var(--unnamed, #2698FF); - font-family: "Roboto"; + color: $main-blue-color; + font-family: "Roboto", sans-serif; font-size: 20px; font-style: normal; font-weight: 700; diff --git a/apps/schools/domains/common/components/select/index.tsx b/apps/schools/domains/common/components/select/index.tsx new file mode 100644 index 00000000..e54b8cc3 --- /dev/null +++ b/apps/schools/domains/common/components/select/index.tsx @@ -0,0 +1,85 @@ +import { Select as BaseSelect } from 'antd' +import React, { useState } from 'react' +import defaultStyles from './styles/default.module.scss' +import { CustomInputProps, selectStyleDictionary } from './interfaces' +import classNames from 'classnames' +import { typeSelect } from '@domains/common/constants/Select' + +export const Select: React.FC = (props) => { + const { + disabled = false, + customType = 'selectDefault', + placeholder, + label, + className, + children, + options, + ...restProps + } = props + + if (!typeSelect.includes(customType)) { + return ( +
+ + + {children} + +
+ ) + } else if (customType === 'selectInput') { + const [addressText, setAddressText] = useState('') + + const handleSearch = (value: string) => { + setAddressText(value) + } + + let additionalOption = + addressText != '' + ? [ + { + value: addressText, + label: addressText, + }, + ] + : [] + + if (options && options.filter((x) => x.value === addressText).length > 0) additionalOption = [] + + return ( +
+ + setAddressText('')} + placeholder={placeholder} + options={options?.concat(additionalOption)} + data-testid='select' + > + {children} + +
+ ) + } else { + return ( +
+ + + {children} + +
+ ) + } +} diff --git a/apps/schools/domains/common/components/select/interfaces.ts b/apps/schools/domains/common/components/select/interfaces.ts new file mode 100644 index 00000000..4be0e7f5 --- /dev/null +++ b/apps/schools/domains/common/components/select/interfaces.ts @@ -0,0 +1,23 @@ +import { SelectProps } from 'antd' +import React from 'react' +import defaultStyles from './styles/default.module.scss' +import multipleStyle from './styles/multiple.module.scss' +import selectInput from './styles/multiple.module.scss' + +export interface CustomInputProps extends SelectProps { + disabled?: boolean + customType?: 'selectMultiple' | 'selectDefault' | 'selectInput' + placeholder?: string + label?: string + children?: React.ReactNode +} + +export interface Dictionary { + [key: string]: any +} + +export const selectStyleDictionary: Dictionary = { + selectMultiple: multipleStyle, + selectDefault: defaultStyles, + selectInput: selectInput, +} diff --git a/apps/schools/domains/common/components/select/styles/default.module.scss b/apps/schools/domains/common/components/select/styles/default.module.scss new file mode 100644 index 00000000..fd9a2eea --- /dev/null +++ b/apps/schools/domains/common/components/select/styles/default.module.scss @@ -0,0 +1,27 @@ +.selectContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .select { + width: 100%; + height: 40px; + margin-top: 8px; + border-radius: 12px; + } + + :global { + .ant-select-selector { + + padding-top: 4px !important; + width: 100% !important; + height: 40px !important; + border-radius: 12px 0 0 12px !important; + } + + .ant-select-selection-search-input { + padding-top: 8px !important; + } + } +} diff --git a/apps/schools/domains/common/components/select/styles/multiple.module.scss b/apps/schools/domains/common/components/select/styles/multiple.module.scss new file mode 100644 index 00000000..11ab954e --- /dev/null +++ b/apps/schools/domains/common/components/select/styles/multiple.module.scss @@ -0,0 +1,14 @@ +.selectContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + :global { + .ant-select-selector { + width: 100% !important; + min-height: 40px !important; + border-radius: 12px !important; + } + } +} diff --git a/apps/schools/domains/common/components/styles/abstracts/_colors.scss b/apps/schools/domains/common/components/styles/abstracts/_colors.scss index f50b1c8a..48b9606d 100644 --- a/apps/schools/domains/common/components/styles/abstracts/_colors.scss +++ b/apps/schools/domains/common/components/styles/abstracts/_colors.scss @@ -19,6 +19,7 @@ $img-shadow: 4px 4px 8px 7px rgba(0, 0, 0, 0.1); //base colors $color-blue: #1890ff; +$main-blue-color: #2698FF; //auth layout colors $color-auth-layout-default: #DEDEDE; diff --git a/apps/schools/domains/common/components/tooltip/styles/styles.module.scss b/apps/schools/domains/common/components/tooltip/styles/styles.module.scss index a2c1c969..d40e9a3b 100644 --- a/apps/schools/domains/common/components/tooltip/styles/styles.module.scss +++ b/apps/schools/domains/common/components/tooltip/styles/styles.module.scss @@ -4,6 +4,7 @@ .tooltipContainer{ height: auto; + width: 3%; } .fieldDiv { diff --git a/apps/schools/domains/common/components/tooltip/withTooltip.tsx b/apps/schools/domains/common/components/tooltip/withTooltip.tsx index c3ad9b6a..b6409a75 100644 --- a/apps/schools/domains/common/components/tooltip/withTooltip.tsx +++ b/apps/schools/domains/common/components/tooltip/withTooltip.tsx @@ -1,6 +1,6 @@ import styles from './styles/styles.module.scss' -import { Row, Tooltip } from 'antd' +import { Row, RowProps, Tooltip } from 'antd' import { QuestionCircleFilled } from '@ant-design/icons' import React from 'react' import { @@ -11,7 +11,7 @@ import { ICON_SIZES, } from '@domains/common/components/tooltip/styles/styles' -interface WithTooltipProps { +interface WithTooltipProps extends RowProps { children: React.ReactNode tooltipText: string iconSize?: 'small' | 'medium' | 'large' @@ -20,7 +20,7 @@ interface WithTooltipProps { overlayInnerColor?: string } -export const WithTooltip: React.FC = (props) => { +export const WithTooltip: React.FC = (params) => { const { children, tooltipText, @@ -28,10 +28,11 @@ export const WithTooltip: React.FC = (props) => { margin = DEFAULT_MARGIN, overlayInnerStyle = DEFAULT_OVERLAY_INNER_STYLE, overlayInnerColor = DEFAULT_OVERLAY_INNER_COLOR, - } = props + ...props + } = params return ( - +
{children}
{ + return value.length >= limit + ? Promise.resolve() + : Promise.reject(new Error(CharactersBottomLimitMsg(limit))) + }, + } +} + +export function getGreaterValidator(limit: number) { + return { + message: CharactersTopLimitMsg(limit), + validator: (_: any, value: any) => { + return value.length <= limit ? Promise.resolve() : Promise.reject(new Error(CharactersTopLimitMsg(limit))) + }, + } +} diff --git a/apps/schools/domains/employee/components/createEmployeeForm/hooks.ts b/apps/schools/domains/employee/components/createEmployeeForm/hooks.ts index 496fb43c..5dd7a424 100644 --- a/apps/schools/domains/employee/components/createEmployeeForm/hooks.ts +++ b/apps/schools/domains/employee/components/createEmployeeForm/hooks.ts @@ -1,8 +1,8 @@ import { useMemo } from 'react' import { EmailIsNotValidMsg, - NameMustContainMsg, - NameMustNotStartOrAndMsg, + PersonNameMustContainMsg, + PersonNameMustNotStartOrAndMsg, PleaseInputYourNameMsg, WrongPhoneFormatMsg, } from '@domains/user/components/auth/constants/message' @@ -25,12 +25,12 @@ export const useCreateEmployeeFormValidators = () => { type: 'string', }, { - message: NameMustContainMsg, + message: PersonNameMustContainMsg, // TODO: move code above regexps to constants pattern: /^[А-Яа-яA-Za-z]+(?: [А-Яа-яA-Za-z]+)*$/, }, { - message: NameMustNotStartOrAndMsg, + message: PersonNameMustNotStartOrAndMsg, // TODO: move code above regexps to constants validator: (_, value) => !/[-]\s|\s[-]/.test(value && value.trim()) ? Promise.resolve() : Promise.reject(), diff --git a/apps/schools/domains/employee/components/createEmployeeForm/styles/styles.ts b/apps/schools/domains/employee/components/createEmployeeForm/styles/styles.ts index 8f60537f..77493475 100644 --- a/apps/schools/domains/employee/components/createEmployeeForm/styles/styles.ts +++ b/apps/schools/domains/employee/components/createEmployeeForm/styles/styles.ts @@ -1 +1 @@ -export const TOOLTIP_MARGIN = '46%' +export const TOOLTIP_MARGIN = '47px' diff --git a/apps/schools/domains/employee/handlers/employee.ts b/apps/schools/domains/employee/handlers/employee.ts index 29b274ad..f1ee25b7 100644 --- a/apps/schools/domains/employee/handlers/employee.ts +++ b/apps/schools/domains/employee/handlers/employee.ts @@ -6,7 +6,7 @@ import { EMPLOYEE_NAME, EMPLOYEE_PHONE, EMPLOYEE_POSITION, -} from '@domains/employee/components/createEmployeeForm/constants' +} from '../components/createEmployeeForm/constants' export async function handleSubmitForm(organizationId: string, formComponent: FormInstance, mutation: any) { const isValid = isPhoneValid(formComponent, EMPLOYEE_PHONE) diff --git a/apps/schools/domains/student/components/createStudentForm/hooks.tsx b/apps/schools/domains/student/components/createStudentForm/hooks.tsx index 11944c33..972f027f 100644 --- a/apps/schools/domains/student/components/createStudentForm/hooks.tsx +++ b/apps/schools/domains/student/components/createStudentForm/hooks.tsx @@ -2,8 +2,8 @@ import { useMemo } from 'react' import { EmailIsNotValidMsg, - NameMustContainMsg, - NameMustNotStartOrAndMsg, + PersonNameMustContainMsg, + PersonNameMustNotStartOrAndMsg, PleaseInputYourNameMsg, PleaseSelectOneOfOptionsMsg, WrongPhoneFormatMsg, @@ -33,12 +33,12 @@ export const useCreateStudentFormValidators = () => { type: 'string', }, { - message: NameMustContainMsg, + message: PersonNameMustContainMsg, // TODO: move code above regexps to constants pattern: /^[А-Яа-яA-Za-z]+(?: [А-Яа-яA-Za-z]+)*$/, }, { - message: NameMustNotStartOrAndMsg, + message: PersonNameMustNotStartOrAndMsg, // TODO: move code above regexps to constants validator: (_, value) => !/[-]\s|\s[-]/.test(value && value.trim()) ? Promise.resolve() : Promise.reject(), diff --git a/apps/schools/domains/student/components/createStudentForm/index.tsx b/apps/schools/domains/student/components/createStudentForm/index.tsx index d8e65f5a..bd58cc70 100644 --- a/apps/schools/domains/student/components/createStudentForm/index.tsx +++ b/apps/schools/domains/student/components/createStudentForm/index.tsx @@ -1,4 +1,4 @@ -import { Form, Select, Typography } from 'antd' +import { Form, Typography } from 'antd' import React, { useState } from 'react' import { Input } from '@domains/common/components/input' import styles from './styles/styles.module.scss' @@ -6,7 +6,7 @@ import { Button } from '@domains/common/components/button' import { useCreateStudentFormValidators } from './hooks' import { useOrganization } from '@domains/organization/providers/organizationProvider' import { useInviteStudentMutation } from '@domains/circle/redux/circleApi' -import { useGetAllCirclesQuery } from '@domains/organization/redux/organizationApi'; +import { useGetAllCirclesQuery } from '@domains/organization/redux/organizationApi' import { isValidFormCheck } from '@domains/common/utils/form' import { CIRCLES, @@ -18,6 +18,7 @@ import { import { handleSubmitForm } from '@domains/student/handlers/student' import { WithTooltip } from '@domains/common/components/tooltip/withTooltip' import { TOOLTIP_MARGIN_TOP } from '@domains/student/components/createStudentForm/styles/constants' +import { Select } from '@domains/common/components/select' export const CreateStudentForm = () => { const validators = useCreateStudentFormValidators() @@ -123,6 +124,7 @@ export const CreateStudentForm = () => { >