mirror of
https://github.com/magnum-opus-nn-cp/frontend.git
synced 2024-11-24 18:43:47 +03:00
refactor: clean up
This commit is contained in:
parent
52c93b18b5
commit
efffd63ae1
|
@ -1,35 +0,0 @@
|
||||||
import {axios} from '../../lib/axios';
|
|
||||||
import {CONVERT_API_URL} from './urlKeys';
|
|
||||||
import {MutationConfig} from '../../lib/react-query';
|
|
||||||
import {useMutation} from '@tanstack/react-query';
|
|
||||||
|
|
||||||
export type ConvertDTO = {
|
|
||||||
pdf: File;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ConvertResponse = {
|
|
||||||
pdf: string;
|
|
||||||
pptx: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const convert = (data: ConvertDTO): Promise<ConvertResponse> => {
|
|
||||||
const inputData = new FormData();
|
|
||||||
inputData.append('pdf', data.pdf);
|
|
||||||
|
|
||||||
return axios.post(CONVERT_API_URL, inputData, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseConvertOptions = {
|
|
||||||
config?: MutationConfig<typeof convert>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useConvert = ({ config }: UseConvertOptions = {}) => {
|
|
||||||
return useMutation({
|
|
||||||
...config,
|
|
||||||
mutationFn: convert
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,65 +0,0 @@
|
||||||
import { useMutation } from '@tanstack/react-query';
|
|
||||||
import { MutationConfig, queryClient } from '../../lib/react-query';
|
|
||||||
import { axios } from '../../lib/axios';
|
|
||||||
import { Answer, PitchDeck } from './types';
|
|
||||||
import { DECKS_API_URL, QUESTION_API_URL, QUESTION_PARAM_DECK_ID, QUESTION_PARAM_QUESTION_ID } from './urlKeys';
|
|
||||||
import { QUERY_KEY_ANSWER } from './queryKeys';
|
|
||||||
|
|
||||||
export type CreateAnswerDTO = {
|
|
||||||
deckId: number;
|
|
||||||
questionId: number;
|
|
||||||
answer: any;
|
|
||||||
isFile?: boolean;
|
|
||||||
file?: File;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CreateAnswerResponse = Answer;
|
|
||||||
|
|
||||||
export const createAnswer = (data: CreateAnswerDTO): Promise<CreateAnswerResponse> => {
|
|
||||||
const path =
|
|
||||||
QUESTION_API_URL.replace(`:${QUESTION_PARAM_DECK_ID}`, String(data.deckId)).replace(
|
|
||||||
`:${QUESTION_PARAM_QUESTION_ID}`,
|
|
||||||
String(data.questionId)
|
|
||||||
) + '/';
|
|
||||||
|
|
||||||
let inputData: any;
|
|
||||||
if (data.isFile) {
|
|
||||||
inputData = new FormData();
|
|
||||||
if (data.answer) {
|
|
||||||
inputData.append('answer', `${data.answer}`);
|
|
||||||
}
|
|
||||||
for (const key in data) {
|
|
||||||
if (key !== 'answer' && key !== 'isFile' && key !== 'deckId' && key !== 'questionId') {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
inputData.append(key, data[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
inputData = {
|
|
||||||
answer: data.answer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return axios.post(path, inputData, {
|
|
||||||
headers: data.isFile
|
|
||||||
? {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseCreateAnswerOptions = {
|
|
||||||
config?: MutationConfig<typeof createAnswer>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useCreateAnswer = ({ config }: UseCreateAnswerOptions = {}) => {
|
|
||||||
return useMutation({
|
|
||||||
onMutate: async () => {
|
|
||||||
await queryClient.cancelQueries([QUERY_KEY_ANSWER]);
|
|
||||||
},
|
|
||||||
...config,
|
|
||||||
mutationFn: createAnswer
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
import {PitchDeck} from './types';
|
|
||||||
import {DECKS_API_URL} from './urlKeys';
|
|
||||||
import {MutationConfig, queryClient} from '../../lib/react-query';
|
|
||||||
import {useMutation} from '@tanstack/react-query';
|
|
||||||
import {QUERY_KEY_DECKS} from './queryKeys';
|
|
||||||
import { axios } from '../../lib/axios';
|
|
||||||
|
|
||||||
export type CreateDeckDTO = Pick<PitchDeck, 'description'>;
|
|
||||||
|
|
||||||
export type CreateDeckResponse = PitchDeck;
|
|
||||||
|
|
||||||
export const createDeck = (data: CreateDeckDTO): Promise<CreateDeckResponse> => {
|
|
||||||
return axios.post(DECKS_API_URL, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseCreateDeckOptions = {
|
|
||||||
config?: MutationConfig<typeof createDeck>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useCreateDeck = ({ config }: UseCreateDeckOptions = {}) => {
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
onMutate: async () => {
|
|
||||||
await queryClient.cancelQueries([QUERY_KEY_DECKS]);
|
|
||||||
},
|
|
||||||
...config,
|
|
||||||
mutationFn: createDeck
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,50 +0,0 @@
|
||||||
import {Question} from './types';
|
|
||||||
import {axios} from '../../lib/axios';
|
|
||||||
import {
|
|
||||||
DECK_API_URL,
|
|
||||||
QUESTION_API_URL,
|
|
||||||
QUESTION_PARAM_DECK_ID,
|
|
||||||
QUESTION_PARAM_QUESTION_ID,
|
|
||||||
} from './urlKeys';
|
|
||||||
import {ExtractFnReturnType, QueryConfig} from '../../lib/react-query';
|
|
||||||
import {useQuery} from '@tanstack/react-query';
|
|
||||||
import {QUERY_KEY_DECKS, QUERY_KEY_QUESTION} from './queryKeys';
|
|
||||||
|
|
||||||
export type GetDeckResponse = {
|
|
||||||
deck: {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
},
|
|
||||||
slides: {
|
|
||||||
slide: number;
|
|
||||||
data: {
|
|
||||||
slug: string;
|
|
||||||
answer: any;
|
|
||||||
photos?: string[]
|
|
||||||
}[];
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDeck = ({ deckId }: { deckId: number }): Promise<GetDeckResponse> => {
|
|
||||||
return axios.get(
|
|
||||||
DECK_API_URL
|
|
||||||
.replace(`:${QUESTION_PARAM_DECK_ID}`, String(deckId))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type QueryFnType = typeof getDeck;
|
|
||||||
|
|
||||||
type UseDeckOptions = {
|
|
||||||
deckId: number;
|
|
||||||
config?: QueryConfig<QueryFnType>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeck = ({ deckId, config }: UseDeckOptions) => {
|
|
||||||
return useQuery<ExtractFnReturnType<QueryFnType>>({
|
|
||||||
...config,
|
|
||||||
queryKey: [QUERY_KEY_DECKS, deckId],
|
|
||||||
queryFn: async () => {
|
|
||||||
return await getDeck({ deckId });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,31 +0,0 @@
|
||||||
import {Question} from './types';
|
|
||||||
import {axios} from '../../lib/axios';
|
|
||||||
import {FIRST_QUESTION_API_URL, FIRST_QUESTION_PARAM} from './urlKeys';
|
|
||||||
import {ExtractFnReturnType, QueryConfig} from '../../lib/react-query';
|
|
||||||
import {useQuery} from '@tanstack/react-query';
|
|
||||||
import {QUERY_KEY_FIRST_QUESTION} from './queryKeys';
|
|
||||||
|
|
||||||
export type GetFirstQuestionResponse = Question;
|
|
||||||
|
|
||||||
export const getFirstQuestion = ({ deckId }: { deckId: number; }): Promise<GetFirstQuestionResponse> => {
|
|
||||||
return axios.get(FIRST_QUESTION_API_URL.replace(`:${FIRST_QUESTION_PARAM}`, String(deckId)));
|
|
||||||
};
|
|
||||||
|
|
||||||
type QueryFnType = typeof getFirstQuestion;
|
|
||||||
|
|
||||||
type UseFirstQuestionOptions = {
|
|
||||||
deckId: number;
|
|
||||||
config?: QueryConfig<QueryFnType>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFirstQuestion = ({ deckId, config }: UseFirstQuestionOptions) => {
|
|
||||||
return useQuery<ExtractFnReturnType<QueryFnType>>({
|
|
||||||
...config,
|
|
||||||
queryKey: [QUERY_KEY_FIRST_QUESTION, deckId],
|
|
||||||
queryFn: async () => {
|
|
||||||
const process = await getFirstQuestion({ deckId });
|
|
||||||
|
|
||||||
return process;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,38 +0,0 @@
|
||||||
import {Question} from './types';
|
|
||||||
import {axios} from '../../lib/axios';
|
|
||||||
import {
|
|
||||||
QUESTION_API_URL,
|
|
||||||
QUESTION_PARAM_DECK_ID,
|
|
||||||
QUESTION_PARAM_QUESTION_ID,
|
|
||||||
} from './urlKeys';
|
|
||||||
import {ExtractFnReturnType, QueryConfig} from '../../lib/react-query';
|
|
||||||
import {useQuery} from '@tanstack/react-query';
|
|
||||||
import {QUERY_KEY_QUESTION} from './queryKeys';
|
|
||||||
|
|
||||||
export type GetQuestionResponse = Question;
|
|
||||||
|
|
||||||
export const getQuestion = ({ deckId, questionId }: { deckId: number; questionId: number; }): Promise<GetQuestionResponse> => {
|
|
||||||
return axios.get(
|
|
||||||
QUESTION_API_URL
|
|
||||||
.replace(`:${QUESTION_PARAM_DECK_ID}`, String(deckId))
|
|
||||||
.replace(`:${QUESTION_PARAM_QUESTION_ID}`, String(questionId))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type QueryFnType = typeof getQuestion;
|
|
||||||
|
|
||||||
type UseQuestionOptions = {
|
|
||||||
deckId: number;
|
|
||||||
questionId: number;
|
|
||||||
config?: QueryConfig<QueryFnType>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useQuestion = ({ deckId, questionId, config }: UseQuestionOptions) => {
|
|
||||||
return useQuery<ExtractFnReturnType<QueryFnType>>({
|
|
||||||
...config,
|
|
||||||
queryKey: [QUERY_KEY_QUESTION, deckId, questionId],
|
|
||||||
queryFn: async () => {
|
|
||||||
return await getQuestion({ deckId, questionId });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,6 +0,0 @@
|
||||||
export * from './types';
|
|
||||||
export * from './urlKeys';
|
|
||||||
export * from './queryKeys';
|
|
||||||
|
|
||||||
export * from './createDeck';
|
|
||||||
export * from './getFirstQuestion';
|
|
|
@ -1,4 +0,0 @@
|
||||||
export const QUERY_KEY_DECKS = 'decks';
|
|
||||||
export const QUERY_KEY_FIRST_QUESTION = 'firstQuestion';
|
|
||||||
export const QUERY_KEY_QUESTION = 'question';
|
|
||||||
export const QUERY_KEY_ANSWER = 'answer';
|
|
|
@ -1,45 +0,0 @@
|
||||||
export enum EntityType {
|
|
||||||
text = 'text', // +
|
|
||||||
number = 'number', // +
|
|
||||||
range = 'range', // +
|
|
||||||
multiple_range = 'multiple_range', // +
|
|
||||||
select = 'select', // +
|
|
||||||
link = 'link', // + добавить валидацию
|
|
||||||
date = 'date', // +
|
|
||||||
// photo = 'photo',
|
|
||||||
multiple_photo = 'multiple_photo',
|
|
||||||
photo_description = 'photo_description',
|
|
||||||
multiple_link_description = 'multiple_link_description', //
|
|
||||||
multiple_photo_description = 'multiple_photo_description',
|
|
||||||
// multiple_links = 'multiple_links', // не пригодилось
|
|
||||||
multiple_date_description = 'multiple_date_description', //
|
|
||||||
text_array = 'text_array', // используется только в подсказке
|
|
||||||
cards = 'cards', // используется только в подсказке
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PitchDeck = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
questions?: any[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Hint = {
|
|
||||||
type: EntityType;
|
|
||||||
value: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Question = {
|
|
||||||
id: number;
|
|
||||||
text: string;
|
|
||||||
type: EntityType;
|
|
||||||
hint: Hint | false;
|
|
||||||
next_id: number;
|
|
||||||
params: { [key: string]: any } | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Answer = {
|
|
||||||
answer: any;
|
|
||||||
deck: number;
|
|
||||||
question: number;
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
export const DECKS_API_URL = '/decks/';
|
|
||||||
|
|
||||||
export const FIRST_QUESTION_PARAM = 'deckId';
|
|
||||||
export const FIRST_QUESTION_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}`;
|
|
||||||
|
|
||||||
export const QUESTION_PARAM_DECK_ID = 'deckId';
|
|
||||||
export const QUESTION_PARAM_QUESTION_ID = 'questionId';
|
|
||||||
export const QUESTION_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}/:${QUESTION_PARAM_QUESTION_ID}`;
|
|
||||||
|
|
||||||
export const DECK_PARAM = 'deckId';
|
|
||||||
export const DECK_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}/presentation`;
|
|
||||||
|
|
||||||
export const CONVERT_API_URL = '/decks/pdf-to-pptx';
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
import { ChatPage } from '../../pages/chat';
|
|
||||||
import { HomePage } from '../../pages/home';
|
import { HomePage } from '../../pages/home';
|
||||||
import { DefaultLayout } from '../../pages/_layouts/DefaultLayout';
|
import { DefaultLayout } from '../../pages/_layouts/DefaultLayout';
|
||||||
import { TextPage } from '../../pages/text';
|
import { TextPage } from '../../pages/text';
|
||||||
import { ResponsePage } from '../../pages/response';
|
import { ResponsePage } from '../../pages/response';
|
||||||
import { CHAT_PAGE_ROUTE, HOME_PAGE_ROUTE, RESPONSE_PAGE_ROUTE, TEXT_PAGE_ROUTE } from './routes';
|
import { HOME_PAGE_ROUTE, RESPONSE_PAGE_ROUTE, TEXT_PAGE_ROUTE } from './routes';
|
||||||
|
|
||||||
export const AppRoutes = () => {
|
export const AppRoutes = () => {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<DefaultLayout />}>
|
<Route element={<DefaultLayout />}>
|
||||||
<Route path={CHAT_PAGE_ROUTE} element={<ChatPage />} />
|
|
||||||
<Route path={HOME_PAGE_ROUTE} element={<HomePage />} />
|
<Route path={HOME_PAGE_ROUTE} element={<HomePage />} />
|
||||||
<Route path={RESPONSE_PAGE_ROUTE} element={<ResponsePage />} />
|
<Route path={RESPONSE_PAGE_ROUTE} element={<ResponsePage />} />
|
||||||
<Route path={TEXT_PAGE_ROUTE} element={<TextPage />} />
|
<Route path={TEXT_PAGE_ROUTE} element={<TextPage />} />
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export const CHAT_PAGE_ROUTE = `/chat`;
|
|
||||||
export const HOME_PAGE_ROUTE = `/`;
|
export const HOME_PAGE_ROUTE = `/`;
|
||||||
|
|
||||||
export const RESPONSE_PAGE_PARAM = 'processId';
|
export const RESPONSE_PAGE_PARAM = 'processId';
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
// export const BACKEND_URL = 'http://192.168.83.181:8000';
|
// export const BACKEND_URL = 'http://192.168.107.4';
|
||||||
// export const BACKEND_URL = 'http://192.168.22.4:8000';
|
export const BACKEND_URL = 'https://4810-176-59-122-187.ngrok-free.app';
|
||||||
// export const BACKEND_URL = 'https://ed68-77-234-219-9.ngrok-free.app';
|
|
||||||
// export const BACKEND_URL = 'https://16c2-77-234-219-9.ngrok-free.app';
|
|
||||||
export const BACKEND_URL = 'http://192.168.107.4';
|
|
||||||
export const BACKEND_MEDIA_PORT = '8000';
|
export const BACKEND_MEDIA_PORT = '8000';
|
||||||
|
|
||||||
export const API_URL = BACKEND_URL + '/api';
|
export const API_URL = BACKEND_URL + '/api';
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
@import 'src/app/styles/vars';
|
|
||||||
|
|
||||||
// Общий лейаут
|
|
||||||
|
|
||||||
.ChatPage {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatPage__inner {
|
|
||||||
position: relative;
|
|
||||||
//width: $breakpoint-tablet-small;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
max-height: 100svh;
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
background-image: url('./assets/background2.jpg');
|
|
||||||
background-size: 150%;
|
|
||||||
|
|
||||||
@include media-down(tablet-small) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatPage__container {
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
//background-color: rgba($color-on-surface-dark-100, 0.6);
|
|
||||||
//background-color: $color-surface-primary;
|
|
||||||
background-color: rgba($color-surface-primary, 0.7);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
padding: $spacing-medium-x $spacing-small 0;
|
|
||||||
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
@include scrollbar;
|
|
||||||
@include flex-col;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инпут
|
|
||||||
|
|
||||||
.ChatPage__inputContainer {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100px;
|
|
||||||
padding: $spacing-small;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba($color-surface-primary, 0.6);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
z-index: 2;
|
|
||||||
@include flex-col;
|
|
||||||
|
|
||||||
max-height: 100svh;
|
|
||||||
overflow-y: auto;
|
|
||||||
@include scrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatPage__skipButton {
|
|
||||||
width: 100% !important;
|
|
||||||
margin-top: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Секция с контентом и сообщениями
|
|
||||||
|
|
||||||
.ChatPage__content {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
//justify-content: flex-end;
|
|
||||||
row-gap: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatPage__message {
|
|
||||||
width: fit-content;
|
|
||||||
max-width: 66%;
|
|
||||||
|
|
||||||
@include media-down(tablet-small) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatPage__message_right {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatPage__message_left {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import s from './ChatPage.module.scss';
|
|
||||||
import {ReactFCC} from '../../utils/ReactFCC';
|
|
||||||
import {ChatContent} from './components/ChatContent';
|
|
||||||
import {ChatItemType, useChatHistory} from './store/history';
|
|
||||||
import {SubmitHandler} from 'react-hook-form';
|
|
||||||
import {getFirstQuestion, useCreateDeck} from '../../api/deck';
|
|
||||||
import {ChatFormInitial} from './components/ChatForm/ChatFormInittial';
|
|
||||||
import {useChatUi} from './hooks/useChatUi';
|
|
||||||
import {useQuestion} from '../../api/deck/getQuestion';
|
|
||||||
import {useCreateAnswer} from '../../api/deck/createAnswer';
|
|
||||||
import {QuestionFactory} from './components/ChatForm/QuestionFactory';
|
|
||||||
import {useSingleTimeout} from '../../hooks/useSingleTimeout';
|
|
||||||
import {usePrevious} from '../../hooks/usePrevious';
|
|
||||||
import {generateAnswerFromData} from './utils/generateAnswerFromData';
|
|
||||||
import {generateTextFromAnswer} from './utils/generateTextFromAnswer';
|
|
||||||
import {Button, ButtonVariant} from '../../components/Button';
|
|
||||||
import {useNavigate} from 'react-router-dom';
|
|
||||||
import {generateFieldsForFileAnswers} from './utils/generateFieldsForFileAnswers';
|
|
||||||
|
|
||||||
export interface ChatPageProps {
|
|
||||||
/**
|
|
||||||
* Дополнительный css-класс
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QUESTION_POLLING_MS = 1000;
|
|
||||||
|
|
||||||
const DEFAULT_DECK_ID = 0;
|
|
||||||
const DEFAULT_QUESTION_ID = 0;
|
|
||||||
|
|
||||||
export const ChatPage: ReactFCC<ChatPageProps> = (props) => {
|
|
||||||
const {className} = props;
|
|
||||||
|
|
||||||
const timeout = useSingleTimeout();
|
|
||||||
|
|
||||||
// Работа с UI
|
|
||||||
const { backgroundRef, containerRef, contentRef, inputContainerRef } = useChatUi();
|
|
||||||
|
|
||||||
const { history, pushHistory } = useChatHistory();
|
|
||||||
const initRef = useRef(false);
|
|
||||||
|
|
||||||
// Устанавливаем первый вопрос в чат
|
|
||||||
useEffect(() => {
|
|
||||||
if (!initRef.current) {
|
|
||||||
pushHistory({
|
|
||||||
type: ChatItemType.receive,
|
|
||||||
text: 'Введите описание проекта (не больше 2-3 предложений)',
|
|
||||||
});
|
|
||||||
initRef.current = true;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Работа с API
|
|
||||||
*/
|
|
||||||
|
|
||||||
const [deckId, setDeckId] = useState(DEFAULT_DECK_ID);
|
|
||||||
const [questionId, setQuestionId] = useState(DEFAULT_QUESTION_ID);
|
|
||||||
|
|
||||||
const { mutateAsync: createDeck } = useCreateDeck();
|
|
||||||
|
|
||||||
const onSubmitInitial: SubmitHandler<any> = useCallback(async (data) => {
|
|
||||||
const deck = await createDeck({
|
|
||||||
description: data.description
|
|
||||||
});
|
|
||||||
setDeckId(deck.id);
|
|
||||||
pushHistory({
|
|
||||||
type: ChatItemType.send,
|
|
||||||
text: deck.description as string
|
|
||||||
});
|
|
||||||
|
|
||||||
const firstQuestion = await getFirstQuestion({ deckId });
|
|
||||||
setQuestionId(firstQuestion.id);
|
|
||||||
}, [createDeck, deckId, pushHistory]);
|
|
||||||
|
|
||||||
// Начинаем пинг-понг вопросов-ответов
|
|
||||||
|
|
||||||
const { data: question, refetch: refetchQuestion } = useQuestion({
|
|
||||||
deckId,
|
|
||||||
questionId,
|
|
||||||
config: {
|
|
||||||
enabled: !!(deckId && questionId),
|
|
||||||
// keepPreviousData: true,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const prevQuestion = usePrevious(question);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (question && question.id !== prevQuestion?.id) {
|
|
||||||
timeout.clear();
|
|
||||||
|
|
||||||
pushHistory({
|
|
||||||
type: ChatItemType.receive,
|
|
||||||
text: question.text
|
|
||||||
});
|
|
||||||
|
|
||||||
const startPolling = () => {
|
|
||||||
timeout.set(async () => {
|
|
||||||
const { data: newQuestion } = await refetchQuestion();
|
|
||||||
if (newQuestion?.hint && !newQuestion.hint.type) {
|
|
||||||
startPolling();
|
|
||||||
}
|
|
||||||
}, QUESTION_POLLING_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (question?.hint && !question.hint.type) {
|
|
||||||
startPolling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [pushHistory, question]);
|
|
||||||
|
|
||||||
|
|
||||||
const { mutateAsync: createAnswer } = useCreateAnswer();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<any> = useCallback(async (data) => {
|
|
||||||
if (!question || !data.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout.clear();
|
|
||||||
|
|
||||||
const answerValue = generateAnswerFromData(question, data);
|
|
||||||
const additionalFields = generateFieldsForFileAnswers(question, data);
|
|
||||||
|
|
||||||
const answer = await createAnswer({
|
|
||||||
deckId,
|
|
||||||
questionId,
|
|
||||||
answer: answerValue,
|
|
||||||
...additionalFields,
|
|
||||||
isFile: !!additionalFields && Object.keys(additionalFields).length !== 0
|
|
||||||
});
|
|
||||||
|
|
||||||
pushHistory({
|
|
||||||
type: ChatItemType.send,
|
|
||||||
text: generateTextFromAnswer(question.type, answer, additionalFields)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (question.next_id) {
|
|
||||||
setQuestionId(question!.next_id);
|
|
||||||
} else {
|
|
||||||
// navigate(DECK_PAGE_ROUTE.replace(`:${DECK_PAGE_PARAM}`, String(deckId)))
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [createAnswer, deckId, pushHistory, question, questionId]);
|
|
||||||
|
|
||||||
// Пропуск вопроса
|
|
||||||
const onSkip = useCallback(() => {
|
|
||||||
if (question && !question.params?.required) {
|
|
||||||
if (question.next_id) {
|
|
||||||
setQuestionId(question!.next_id);
|
|
||||||
} else {
|
|
||||||
// navigate(DECK_PAGE_ROUTE.replace(`:${DECK_PAGE_PARAM}`, String(deckId)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [deckId, navigate, question]);
|
|
||||||
|
|
||||||
// ---------- Скролл чата ----------
|
|
||||||
// todo при печатании текста тоже двигать скролл
|
|
||||||
useEffect(() => {
|
|
||||||
if (contentRef.current && inputContainerRef.current) {
|
|
||||||
contentRef.current.style.paddingBottom = inputContainerRef.current.scrollHeight + 'px';
|
|
||||||
containerRef.current?.scrollTo({ top: contentRef.current.scrollHeight });
|
|
||||||
}
|
|
||||||
}, [containerRef, contentRef, history, question, inputContainerRef]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatPage, className)}>
|
|
||||||
<div className={s.ChatPage__inner} ref={backgroundRef}>
|
|
||||||
<div className={s.ChatPage__container} ref={containerRef}>
|
|
||||||
<div className={s.ChatPage__content} ref={contentRef}>
|
|
||||||
<ChatContent history={history} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={s.ChatPage__inputContainer} ref={inputContainerRef}>
|
|
||||||
{question ? (
|
|
||||||
<>
|
|
||||||
<QuestionFactory onSubmit={onSubmit} {...question} />
|
|
||||||
{!question.params?.required && (
|
|
||||||
<Button
|
|
||||||
className={s.ChatPage__skipButton}
|
|
||||||
variant={ButtonVariant.secondary}
|
|
||||||
onClick={() => onSkip()}
|
|
||||||
>Пропустить</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : !deckId ? (
|
|
||||||
<ChatFormInitial onSubmit={onSubmitInitial} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
Binary file not shown.
Before Width: | Height: | Size: 102 KiB |
|
@ -1,86 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import {ReactFCC} from '../../../utils/ReactFCC';
|
|
||||||
import s from '../ChatPage.module.scss';
|
|
||||||
import {Message, MessageType, MessageVariant} from '../../../components/Message';
|
|
||||||
import {Fragment, memo} from 'react';
|
|
||||||
import {mediaQuery, useMediaQueryResult} from '../../../hooks/useMediaQueryResult';
|
|
||||||
import {ChatItem, ChatItemType} from '../store/history';
|
|
||||||
|
|
||||||
export interface ChatMockProps {
|
|
||||||
/**
|
|
||||||
* Дополнительный css-класс
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
history: ChatItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatContent: ReactFCC<ChatMockProps> = memo(function ChatMock(props) {
|
|
||||||
const { history } = props;
|
|
||||||
|
|
||||||
const isLarge = useMediaQueryResult(mediaQuery.desktopMediumUp);
|
|
||||||
const messageTypeForSend = isLarge ? MessageType.left : MessageType.right;
|
|
||||||
const messageTypeClassForSend = isLarge ? s.ChatPage__message_left : s.ChatPage__message_right;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{history.map((item, index) => (
|
|
||||||
<Message
|
|
||||||
className={clsx(s.ChatPage__message, {
|
|
||||||
[messageTypeClassForSend]: item.type === ChatItemType.send,
|
|
||||||
[s.ChatPage__message_left]: item.type === ChatItemType.receive,
|
|
||||||
})}
|
|
||||||
type={item.type === ChatItemType.send ? messageTypeForSend : MessageType.left}
|
|
||||||
variant={item.type === ChatItemType.send ? MessageVariant.primary : MessageVariant.secondary}
|
|
||||||
key={index}>
|
|
||||||
{item.text}
|
|
||||||
</Message>
|
|
||||||
))}
|
|
||||||
{/*{Array(5).fill(null).map((_, index) => (*/}
|
|
||||||
{/* <Fragment key={index}>*/}
|
|
||||||
{/* <Message*/}
|
|
||||||
{/* className={clsx(s.ChatPage__message, s.ChatPage__message_left)}*/}
|
|
||||||
{/* type={MessageType.left}*/}
|
|
||||||
{/* variant={MessageVariant.secondary}>*/}
|
|
||||||
{/* Какие метрики вы используете чтобы отслеживать прогресс развития проекта?*/}
|
|
||||||
{/* </Message>*/}
|
|
||||||
|
|
||||||
{/* <Message*/}
|
|
||||||
{/* className={clsx(s.ChatPage__message, messageTypeClassForSend)}*/}
|
|
||||||
{/* type={messageTypeForSend}*/}
|
|
||||||
{/* variant={MessageVariant.primary}>*/}
|
|
||||||
{/* Возможными метриками для отслеживания прогресса могут быть: количество скачиваний и использования приложения/сервиса, количество активных пользователей, уровень удовлетворенности пользователей, объем продаж/дохода, показатели роста/расширения компании и др.*/}
|
|
||||||
{/* </Message>*/}
|
|
||||||
|
|
||||||
{/* <Message*/}
|
|
||||||
{/* className={clsx(s.ChatPage__message, s.ChatPage__message_left)}*/}
|
|
||||||
{/* type={MessageType.left}*/}
|
|
||||||
{/* variant={MessageVariant.secondary}>*/}
|
|
||||||
{/* На чем вы зарабатываете? Сколько и за что вам платят клиенты*/}
|
|
||||||
{/* </Message>*/}
|
|
||||||
|
|
||||||
{/* <Message*/}
|
|
||||||
{/* className={clsx(s.ChatPage__message, messageTypeClassForSend)}*/}
|
|
||||||
{/* type={messageTypeForSend}*/}
|
|
||||||
{/* variant={MessageVariant.primary}>*/}
|
|
||||||
{/* Проект может зарабатывать на платной подписке*/}
|
|
||||||
{/* </Message>*/}
|
|
||||||
|
|
||||||
{/* <Message*/}
|
|
||||||
{/* className={clsx(s.ChatPage__message, s.ChatPage__message_left)}*/}
|
|
||||||
{/* type={MessageType.left}*/}
|
|
||||||
{/* variant={MessageVariant.secondary}>*/}
|
|
||||||
{/* Какие метрики вы используете чтобы отслеживать прогресс развития проекта?*/}
|
|
||||||
{/* </Message>*/}
|
|
||||||
|
|
||||||
{/* <Message*/}
|
|
||||||
{/* className={clsx(s.ChatPage__message, messageTypeClassForSend)}*/}
|
|
||||||
{/* type={messageTypeForSend}*/}
|
|
||||||
{/* variant={MessageVariant.primary}>*/}
|
|
||||||
{/* Возможными метриками для отслеживания прогресса могут быть: количество скачиваний и использования приложения/сервиса, количество активных пользователей, уровень удовлетворенности пользователей, объем продаж/дохода, показатели роста/расширения компании и др.*/}
|
|
||||||
{/* </Message>*/}
|
|
||||||
{/* </Fragment>*/}
|
|
||||||
{/*))}*/}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
|
||||||
import s from './components/ChatFormText/ChatFormText.module.scss';
|
|
||||||
import {Textarea} from '../../../../components/Textarea';
|
|
||||||
import {KeyboardEvent} from 'react';
|
|
||||||
import {isKey} from '../../../../utils/isKey';
|
|
||||||
import {Key} from 'ts-key-enum';
|
|
||||||
import {Button} from '../../../../components/Button';
|
|
||||||
import {Form} from '../../../../components/Form';
|
|
||||||
import {SubmitHandler} from 'react-hook-form';
|
|
||||||
import {ReactComponent as RightIcon} from '../../../../assets/icons/right.svg';
|
|
||||||
|
|
||||||
export interface ChatFormInitialProps {
|
|
||||||
/**
|
|
||||||
* Дополнительный css-класс
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
onSubmit: SubmitHandler<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormInitial: ReactFCC<ChatFormInitialProps> = (props) => {
|
|
||||||
const {onSubmit} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
{({ register, handleSubmit }) => {
|
|
||||||
return (
|
|
||||||
<div className={s.ChatFormText__richInput}>
|
|
||||||
<Textarea
|
|
||||||
className={s.ChatFormText__input}
|
|
||||||
placeholder={'Введите сообщение'}
|
|
||||||
rows={1}
|
|
||||||
cols={33}
|
|
||||||
onKeyDown={(e: KeyboardEvent) => {
|
|
||||||
if (isKey(e.nativeEvent, Key.Enter)) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleSubmit(onSubmit)(e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
registration={register('description', {
|
|
||||||
required: true,
|
|
||||||
max: 1000,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button className={s.ChatFormText__richInputButton} onClick={handleSubmit(onSubmit)}>
|
|
||||||
<RightIcon className={s.ChatFormText__buttonIcon} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,244 +0,0 @@
|
||||||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
|
||||||
import {EntityType, Question} from '../../../../api/deck';
|
|
||||||
import {Form} from '../../../../components/Form';
|
|
||||||
import {ChatFormText} from './components/ChatFormText';
|
|
||||||
import {ChatFormSelect} from './components/ChatFormSelect';
|
|
||||||
import {ChatFormMultipleRange, RangeType} from './components/ChatFormMultipleRange';
|
|
||||||
import {ChatFormMultipleDateDescription} from './components/ChatFormMultipleDateDescription';
|
|
||||||
import {ChatFormRange} from './components/ChatFormRange';
|
|
||||||
import {ChatFormMultipleLinkDescription} from './components/ChatFormMultipleLinkDescription';
|
|
||||||
import {ChatFormPhotoDescription} from './components/ChatFormPhotoDescription';
|
|
||||||
import {ChatFormMultiplePhoto} from './components/ChatFormMultiplePhoto';
|
|
||||||
import {ChatFormMultiplePhotoDescription} from './components/ChatFormMultiplePhotoDescription';
|
|
||||||
|
|
||||||
export interface QuestionFactoryProps {
|
|
||||||
type: EntityType;
|
|
||||||
params: Question['params'];
|
|
||||||
onSubmit: (data: any) => void;
|
|
||||||
hint?: Question['hint'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const QuestionFactory: ReactFCC<QuestionFactoryProps> = (props) => {
|
|
||||||
const {type, params, onSubmit, hint} = props;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case EntityType.text:
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
{({ handleSubmit, register, setValue }) => (
|
|
||||||
<ChatFormText
|
|
||||||
type={'textarea'}
|
|
||||||
registration={register('value', {
|
|
||||||
required: true,
|
|
||||||
maxLength: params?.max_length
|
|
||||||
})}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(val) => setValue('value', val)}
|
|
||||||
hint={hint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.number:
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
{({ handleSubmit, register, setValue }) => (
|
|
||||||
<ChatFormText
|
|
||||||
type={'number'}
|
|
||||||
registration={register('value', {
|
|
||||||
required: true,
|
|
||||||
maxLength: params?.max_length
|
|
||||||
})}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(val) => setValue('value', val)}
|
|
||||||
hint={hint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.date:
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
{({ handleSubmit, register, setValue }) => (
|
|
||||||
<ChatFormText
|
|
||||||
type={'date'}
|
|
||||||
registration={register('value', {
|
|
||||||
required: true,
|
|
||||||
maxLength: params?.max_length
|
|
||||||
})}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(val) => setValue('value', val)}
|
|
||||||
hint={hint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.link:
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
{({ handleSubmit, register, setValue }) => (
|
|
||||||
<ChatFormText
|
|
||||||
type={'text'}
|
|
||||||
registration={register('value', {
|
|
||||||
required: true,
|
|
||||||
maxLength: params?.max_length
|
|
||||||
})}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(val) => setValue('value', val)}
|
|
||||||
hint={hint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.select:
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
{({ handleSubmit, register, control }) => (
|
|
||||||
<ChatFormSelect
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control}
|
|
||||||
options={params?.options || []}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
case EntityType.multiple_range:
|
|
||||||
return (
|
|
||||||
<Form options={{
|
|
||||||
values: {
|
|
||||||
value: {
|
|
||||||
...(params?.scrollbars || []).reduce((acc: any, i: RangeType) => {
|
|
||||||
acc[i.slug] = i.min_value;
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{({ handleSubmit, register, control, setValue }) => (
|
|
||||||
<ChatFormMultipleRange
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control as any}
|
|
||||||
scrollbars={params?.scrollbars || []}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(value) => setValue('value', value)}
|
|
||||||
hint={hint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.multiple_date_description:
|
|
||||||
return (
|
|
||||||
<Form options={{
|
|
||||||
values: {
|
|
||||||
value: {
|
|
||||||
[new Date().toISOString()]: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{({ handleSubmit, register, control, setValue }) => (
|
|
||||||
<ChatFormMultipleDateDescription
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control as any}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(value) => setValue('value', value)}
|
|
||||||
hint={hint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.range:
|
|
||||||
return (
|
|
||||||
<Form options={{
|
|
||||||
values: {
|
|
||||||
value: params?.min_value ?? 0
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{({ handleSubmit, register, control, setValue }) => (
|
|
||||||
<ChatFormRange
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control as any}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(value) => setValue('value', value)}
|
|
||||||
hint={hint}
|
|
||||||
range={params as RangeType}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.multiple_link_description:
|
|
||||||
return (
|
|
||||||
<Form options={{
|
|
||||||
values: {
|
|
||||||
value: {
|
|
||||||
'Ссылка': '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{({ handleSubmit, register, control, setValue }) => (
|
|
||||||
<ChatFormMultipleLinkDescription
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control as any}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
setValue={(value) => setValue('value', value)}
|
|
||||||
hint={hint}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
case EntityType.photo_description:
|
|
||||||
return (
|
|
||||||
<Form options={{
|
|
||||||
values: {
|
|
||||||
value: {
|
|
||||||
file: null,
|
|
||||||
text: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{({ handleSubmit, register, control }) => (
|
|
||||||
<ChatFormPhotoDescription
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control as any}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
case EntityType.multiple_photo:
|
|
||||||
return (
|
|
||||||
<Form options={{
|
|
||||||
values: {
|
|
||||||
value: {}
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{({ handleSubmit, register, control }) => (
|
|
||||||
<ChatFormMultiplePhoto
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control as any}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
case EntityType.multiple_photo_description:
|
|
||||||
return (
|
|
||||||
<Form options={{
|
|
||||||
values: {
|
|
||||||
value: []
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{({ handleSubmit, register, control }) => (
|
|
||||||
<ChatFormMultiplePhotoDescription
|
|
||||||
registration={register('value', { required: true })}
|
|
||||||
control={control as any}
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
@import 'src/app/styles/vars';
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription {
|
|
||||||
@include flex-col;
|
|
||||||
gap: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription__items {
|
|
||||||
@include flex-col;
|
|
||||||
gap: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription__group {
|
|
||||||
@include flex-col;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: $spacing-small-3x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription__input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription__textarea {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription__buttons {
|
|
||||||
@include flex;
|
|
||||||
gap: $spacing-small-3x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription__hint {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleDateDescription__itemName {
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
|
||||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
|
||||||
import {SimpleButton} from '../../../SimpleButton';
|
|
||||||
import s from './ChatFormMultipleDateDescription.module.scss';
|
|
||||||
import {Hint} from '../../../../../../api/deck';
|
|
||||||
import {Input} from '../../../../../../components/Input';
|
|
||||||
import {Textarea} from '../../../../../../components/Textarea';
|
|
||||||
import {Button, ButtonVariant} from '../../../../../../components/Button';
|
|
||||||
import {Hint as HintCmp, HintsContainer} from '../../../../../../components/Hint';
|
|
||||||
import {formatDate} from '../../../../../../utils/fomat';
|
|
||||||
import format from 'date-fns/format';
|
|
||||||
|
|
||||||
export interface ChatFormMultipleDateDescriptionProps {
|
|
||||||
className?: string;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
setValue: (value: any) => void;
|
|
||||||
hint?: Hint | false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormMultipleDateDescription: ReactFCC<ChatFormMultipleDateDescriptionProps> = (props) => {
|
|
||||||
const {className, registration, control, onSubmit, hint, setValue} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormMultipleDateDescription, className)}>
|
|
||||||
<Controller control={control} render={({ field: { value, onChange }}) => (
|
|
||||||
<>
|
|
||||||
<HintsContainer isLoading={hint && !hint.value}>
|
|
||||||
{hint && hint.value && (
|
|
||||||
<HintCmp
|
|
||||||
className={s.ChatFormMultipleDateDescription__hint}
|
|
||||||
onClick={() => {
|
|
||||||
const newValue: any = {};
|
|
||||||
for (const date in hint.value) {
|
|
||||||
newValue[format(new Date(date),'yyyy-MM-dd')] = hint.value[date]
|
|
||||||
}
|
|
||||||
setValue({ ...hint.value })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Object.entries(hint.value).map(([key, val]) => `${formatDate(key)}: ${val}`).join('\n')}
|
|
||||||
</HintCmp>
|
|
||||||
)}
|
|
||||||
</HintsContainer>
|
|
||||||
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__items}>
|
|
||||||
{Object.entries(value).map(([date, text]: any, index, { length: arrLength }) => {
|
|
||||||
return (
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__group} key={index}>
|
|
||||||
<Input className={s.ChatFormMultipleDateDescription__input}
|
|
||||||
type={'date'}
|
|
||||||
value={format(new Date(date),'yyyy-MM-dd')}
|
|
||||||
onChange={(e) => {
|
|
||||||
const newValue = { ...value };
|
|
||||||
const text = newValue[date];
|
|
||||||
delete newValue[date];
|
|
||||||
onChange({ ...newValue, [new Date(e.target.value).toISOString()]: text })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Textarea
|
|
||||||
rows={1}
|
|
||||||
className={s.ChatFormMultipleDateDescription__textarea}
|
|
||||||
placeholder={'Текст'}
|
|
||||||
value={text}
|
|
||||||
onChange={(e) => {
|
|
||||||
onChange({ ...value, [date]: e.target.value })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__buttons}>
|
|
||||||
<Button variant={ButtonVariant.secondary} onClick={() => {
|
|
||||||
const newValue = { ...value };
|
|
||||||
delete newValue[date];
|
|
||||||
if (Object.keys(newValue).length !== 0) {
|
|
||||||
onChange({ ...newValue })
|
|
||||||
}
|
|
||||||
}}>Удалить</Button>
|
|
||||||
{index === arrLength - 1 && (
|
|
||||||
<Button variant={ButtonVariant.secondary}
|
|
||||||
onClick={() => {
|
|
||||||
onChange({ ...value, [new Date().toISOString()]: '' })
|
|
||||||
}}
|
|
||||||
>Добавить</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SimpleButton className={s.ChatFormMultipleRange__button} onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)} name={registration.name!} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormMultipleDateDescription';
|
|
|
@ -1,93 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
|
||||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
|
||||||
import {SimpleButton} from '../../../SimpleButton';
|
|
||||||
import s from '../ChatFormMultipleDateDescription/ChatFormMultipleDateDescription.module.scss';
|
|
||||||
import {Hint} from '../../../../../../api/deck';
|
|
||||||
import {Input} from '../../../../../../components/Input';
|
|
||||||
import {Textarea} from '../../../../../../components/Textarea';
|
|
||||||
import {Button, ButtonVariant} from '../../../../../../components/Button';
|
|
||||||
import {Hint as HintCmp, HintsContainer} from '../../../../../../components/Hint';
|
|
||||||
|
|
||||||
export interface ChatFormMultipleLinkDescriptionProps {
|
|
||||||
className?: string;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
setValue: (value: any) => void;
|
|
||||||
hint?: Hint | false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormMultipleLinkDescription: ReactFCC<ChatFormMultipleLinkDescriptionProps> = (props) => {
|
|
||||||
const {className, registration, control, onSubmit, hint, setValue} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormMultipleDateDescription, className)}>
|
|
||||||
<Controller control={control} render={({ field: { value, onChange }}) => (
|
|
||||||
<>
|
|
||||||
{/*<HintsContainer isLoading={hint && !hint.value}>*/}
|
|
||||||
{/* {hint && hint.value && (*/}
|
|
||||||
{/* <HintCmp*/}
|
|
||||||
{/* className={s.ChatFormMultipleDateDescription__hint}*/}
|
|
||||||
{/* onClick={() => setValue({ ...hint.value })}*/}
|
|
||||||
{/* >*/}
|
|
||||||
{/* {Object.entries(hint.value).map(([key, val]) => `${key}: ${val}`).join('\n')}*/}
|
|
||||||
{/* </HintCmp>*/}
|
|
||||||
{/* )}*/}
|
|
||||||
{/*</HintsContainer>*/}
|
|
||||||
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__items}>
|
|
||||||
{Object.entries(value).map(([link, text]: any, index, { length: arrLength }) => {
|
|
||||||
return (
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__group} key={index}>
|
|
||||||
<Input
|
|
||||||
className={s.ChatFormMultipleDateDescription__input}
|
|
||||||
type={'text'}
|
|
||||||
placeholder={'Ссылка'}
|
|
||||||
value={link}
|
|
||||||
onChange={(e) => {
|
|
||||||
const newValue = { ...value };
|
|
||||||
const text = newValue[link];
|
|
||||||
delete newValue[link];
|
|
||||||
onChange({ ...newValue, [e.target.value]: text })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Textarea
|
|
||||||
rows={1}
|
|
||||||
className={s.ChatFormMultipleDateDescription__textarea}
|
|
||||||
placeholder={'Текст'}
|
|
||||||
value={text}
|
|
||||||
onChange={(e) => {
|
|
||||||
onChange({ ...value, [link]: e.target.value })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__buttons}>
|
|
||||||
{Object.entries(value).filter((i) => i[0] !== link).length !== 0 && (
|
|
||||||
<Button variant={ButtonVariant.secondary} onClick={() => {
|
|
||||||
const newValue = { ...value };
|
|
||||||
delete newValue[link];
|
|
||||||
if (Object.keys(newValue).length !== 0) {
|
|
||||||
onChange({ ...newValue })
|
|
||||||
}
|
|
||||||
}}>Удалить</Button>
|
|
||||||
)}
|
|
||||||
{index === arrLength - 1 && (
|
|
||||||
<Button variant={ButtonVariant.secondary}
|
|
||||||
onClick={() => {
|
|
||||||
onChange({ ...value, 'Link': '' })
|
|
||||||
}}
|
|
||||||
>Добавить</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SimpleButton className={s.ChatFormMultipleRange__button} onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)} name={registration.name!} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormMultipleLinkDescription';
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { ChangeEvent } from 'react';
|
|
||||||
import { Control, Controller, FieldValues, UseFormRegisterReturn } from 'react-hook-form';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { ReactFCC } from '../../../../../../utils/ReactFCC';
|
|
||||||
import s from '../ChatFormPhotoDescription/ChatFormPhotoDescription.module.scss';
|
|
||||||
import { Upload } from '../../../../../../components/Upload';
|
|
||||||
import { SimpleButton } from '../../../SimpleButton';
|
|
||||||
|
|
||||||
export interface ChatFormMultiplePhotoProps {
|
|
||||||
className?: string;
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormMultiplePhoto: ReactFCC<ChatFormMultiplePhotoProps> = (props) => {
|
|
||||||
const { className, onSubmit, registration, control } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormPhotoDescription, className)}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
|
||||||
<>
|
|
||||||
{value && Object.values(value).map((file: any, index) => <p key={index}>Загружен файл: {file.name}</p>)}
|
|
||||||
|
|
||||||
<Upload
|
|
||||||
multiple={true}
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (!e.target.files) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const files: any = { ...value };
|
|
||||||
Array.from(e.target.files).forEach((file, index) => {
|
|
||||||
files[`file_${Object.keys(files).length + 1}`] = file;
|
|
||||||
});
|
|
||||||
|
|
||||||
onChange({ ...files });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SimpleButton onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
name={registration.name!}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormMultiplePhoto';
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { ChangeEvent } from 'react';
|
|
||||||
import { Control, Controller, FieldValues, UseFormRegisterReturn } from 'react-hook-form';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { ReactFCC } from '../../../../../../utils/ReactFCC';
|
|
||||||
import { SimpleButton } from '../../../SimpleButton';
|
|
||||||
import s from '../ChatFormMultipleDateDescription/ChatFormMultipleDateDescription.module.scss';
|
|
||||||
import { Textarea } from '../../../../../../components/Textarea';
|
|
||||||
import { Button, ButtonVariant } from '../../../../../../components/Button';
|
|
||||||
import { Upload } from '../../../../../../components/Upload';
|
|
||||||
|
|
||||||
export interface ChatFormMultiplePhotoDescriptionProps {
|
|
||||||
className?: string;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormMultiplePhotoDescription: ReactFCC<ChatFormMultiplePhotoDescriptionProps> = (props) => {
|
|
||||||
const { className, registration, control, onSubmit } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormMultipleDateDescription, className)}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
|
||||||
<>
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__items}>
|
|
||||||
{value.map((item: any, index: number, { length: arrLength }: any) => {
|
|
||||||
return (
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__group} key={index}>
|
|
||||||
{item.file && (
|
|
||||||
<p className={s.ChatFormMultipleDateDescription__itemName}>Загружен файл: {item.file.name}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Textarea
|
|
||||||
rows={1}
|
|
||||||
className={s.ChatFormMultipleDateDescription__textarea}
|
|
||||||
placeholder={'Описание'}
|
|
||||||
value={item.text}
|
|
||||||
onChange={(e) => {
|
|
||||||
const itemIndex = value.indexOf(item);
|
|
||||||
const newItem = { ...item, text: e.target.value };
|
|
||||||
onChange([...value.slice(0, itemIndex), newItem, ...value.slice(itemIndex + 1)]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className={s.ChatFormMultipleDateDescription__buttons}>
|
|
||||||
<Button
|
|
||||||
variant={ButtonVariant.secondary}
|
|
||||||
onClick={() => {
|
|
||||||
const newValue = value.filter((i: any) => i !== item);
|
|
||||||
if (Object.keys(newValue).length !== 0) {
|
|
||||||
onChange([...newValue]);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
Удалить
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Upload
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange([...value, { file, text: '' }]);
|
|
||||||
e.target.value = '';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SimpleButton className={s.ChatFormMultipleRange__button} onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
name={registration.name!}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormMultiplePhotoDescription';
|
|
|
@ -1,20 +0,0 @@
|
||||||
@import 'src/app/styles/vars';
|
|
||||||
|
|
||||||
.ChatFormMultipleRange {
|
|
||||||
@include flex-col;
|
|
||||||
max-width: unset;
|
|
||||||
align-items: stretch;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
gap: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleRange__items {
|
|
||||||
@include flex-col;
|
|
||||||
gap: $spacing-small;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormMultipleRange__button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
|
||||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
|
||||||
import {SimpleButton} from '../../../SimpleButton';
|
|
||||||
import s from './ChatFormMultipleRange.module.scss';
|
|
||||||
import {Range} from '../../../../../../components/Range';
|
|
||||||
import {Hint} from '../../../../../../api/deck';
|
|
||||||
import {Hint as HintCmp, HintsContainer} from '../../../../../../components/Hint';
|
|
||||||
import {currencyFormatter} from '../../../../../../utils/fomat';
|
|
||||||
|
|
||||||
export interface RangeType {
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
min_value: number;
|
|
||||||
max_value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatFormSelectProps {
|
|
||||||
className?: string;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
scrollbars: RangeType[];
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
setValue: (value: any) => void;
|
|
||||||
hint?: Hint | false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const slugsForFormat = ['sam', 'som', 'tam', 'sum', 'cac', 'ltv'];
|
|
||||||
|
|
||||||
export const ChatFormMultipleRange: ReactFCC<ChatFormSelectProps> = (props) => {
|
|
||||||
const {className, registration, control, scrollbars, onSubmit, hint, setValue} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormMultipleRange, className)}>
|
|
||||||
<Controller control={control} render={({ field: { value, onChange }}) => (
|
|
||||||
<>
|
|
||||||
<HintsContainer isLoading={hint && !hint.value}>
|
|
||||||
{hint && hint.value && Object.entries(hint.value).map(([key, val], index) => {
|
|
||||||
const range = scrollbars.find((j) => j.slug === key);
|
|
||||||
return range ? (
|
|
||||||
<HintCmp onClick={() => setValue({ ...value, [key]: val })} key={index}>{`${range.name}: ${slugsForFormat.includes(range.slug) ? currencyFormatter.format(val as any) : val}`}</HintCmp>
|
|
||||||
) : null
|
|
||||||
})}
|
|
||||||
</HintsContainer>
|
|
||||||
|
|
||||||
<div className={s.ChatFormMultipleRange__items}>
|
|
||||||
{scrollbars.map((item, index) => (
|
|
||||||
<Range
|
|
||||||
label={item.name} min={item.min_value} max={item.max_value} step={((item.max_value - item.min_value) / 100) || 100}
|
|
||||||
value={value[item.slug]}
|
|
||||||
onChange={(val) => onChange({ ...value, [item.slug]: val })}
|
|
||||||
format={slugsForFormat.includes(item.slug)}
|
|
||||||
key={index} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SimpleButton className={s.ChatFormMultipleRange__button} onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)} name={registration.name!} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormMultipleRange';
|
|
|
@ -1,40 +0,0 @@
|
||||||
@import 'src/app/styles/vars';
|
|
||||||
|
|
||||||
.ChatFormPhotoDescription {
|
|
||||||
@include flex-col;
|
|
||||||
gap: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
//.ChatFormText__richInput {
|
|
||||||
// @include flex;
|
|
||||||
// position: relative;
|
|
||||||
// width: 100%;
|
|
||||||
// min-height: $chat-input-height;
|
|
||||||
// height: 100%;
|
|
||||||
// align-items: stretch;
|
|
||||||
//
|
|
||||||
// &::before {
|
|
||||||
// content: '';
|
|
||||||
// position: absolute;
|
|
||||||
// width: 100%;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//.ChatFormText__input {
|
|
||||||
// border-radius: $chat-input-radius 0 0 $chat-input-radius !important;
|
|
||||||
// flex: 1;
|
|
||||||
// min-height: 50px !important;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//.ChatFormText__richInputButton {
|
|
||||||
// width: $chat-input-height !important;
|
|
||||||
// height: auto !important;
|
|
||||||
// border-radius: 0 $chat-input-radius $chat-input-radius 0 !important;
|
|
||||||
// padding: 0 !important;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//.ChatFormText__buttonIcon {
|
|
||||||
// width: 24px;
|
|
||||||
// height: 24px;
|
|
||||||
//}
|
|
|
@ -1,55 +0,0 @@
|
||||||
import { ChangeEvent } from 'react';
|
|
||||||
import { Control, Controller, FieldValues, UseFormRegisterReturn } from 'react-hook-form';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { ReactFCC } from '../../../../../../utils/ReactFCC';
|
|
||||||
import { Textarea } from '../../../../../../components/Textarea';
|
|
||||||
import { Upload } from '../../../../../../components/Upload';
|
|
||||||
import { SimpleButton } from '../../../SimpleButton';
|
|
||||||
import s from './ChatFormPhotoDescription.module.scss';
|
|
||||||
|
|
||||||
export interface ChatFormPhotoDescriptionProps {
|
|
||||||
className?: string;
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormPhotoDescription: ReactFCC<ChatFormPhotoDescriptionProps> = (props) => {
|
|
||||||
const { className, onSubmit, registration, control } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormPhotoDescription, className)}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
|
||||||
<>
|
|
||||||
{value.file && <p>Загружен файл: {value.file.name}</p>}
|
|
||||||
|
|
||||||
<Upload
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange({ ...value, file });
|
|
||||||
e.target.value = '';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Textarea
|
|
||||||
placeholder={'Текст'}
|
|
||||||
value={value.text}
|
|
||||||
onChange={(e) => {
|
|
||||||
onChange({ ...value, text: e.target.value });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SimpleButton onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
name={registration.name!}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormPhotoDescription';
|
|
|
@ -1,52 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
|
||||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
|
||||||
import {SimpleButton} from '../../../SimpleButton';
|
|
||||||
import s from './../ChatFormMultipleRange/ChatFormMultipleRange.module.scss';
|
|
||||||
import {Range} from '../../../../../../components/Range';
|
|
||||||
import {Hint} from '../../../../../../api/deck';
|
|
||||||
import {Hint as HintCmp, HintsContainer} from '../../../../../../components/Hint';
|
|
||||||
import {RangeType, slugsForFormat} from '../ChatFormMultipleRange';
|
|
||||||
import {currencyFormatter} from '../../../../../../utils/fomat';
|
|
||||||
|
|
||||||
export interface ChatFormRangeProps {
|
|
||||||
className?: string;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
setValue: (value: any) => void;
|
|
||||||
hint?: Hint | false;
|
|
||||||
range: RangeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormRange: ReactFCC<ChatFormRangeProps> = (props) => {
|
|
||||||
const {className, registration, control, onSubmit, hint, setValue, range} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormMultipleRange, className)}>
|
|
||||||
<Controller control={control} render={({ field: { value, onChange }}) => (
|
|
||||||
<>
|
|
||||||
<HintsContainer isLoading={hint && !hint.value}>
|
|
||||||
{hint && hint.value && (
|
|
||||||
<HintCmp onClick={() => setValue(hint.value)}>
|
|
||||||
{slugsForFormat.includes(range.slug) ? currencyFormatter.format(hint.value) : hint.value}
|
|
||||||
</HintCmp>
|
|
||||||
)}
|
|
||||||
</HintsContainer>
|
|
||||||
|
|
||||||
<div className={s.ChatFormMultipleRange__items}>
|
|
||||||
<Range
|
|
||||||
value={value}
|
|
||||||
min={range.min_value} max={range.max_value} step={((range.max_value - range.min_value) / 100) || 100}
|
|
||||||
onChange={(val) => onChange(val)}
|
|
||||||
format={slugsForFormat.includes(range.slug)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SimpleButton className={s.ChatFormMultipleRange__button} onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)} name={registration.name!} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormRange';
|
|
|
@ -1,12 +0,0 @@
|
||||||
@import 'src/app/styles/vars';
|
|
||||||
|
|
||||||
.ChatFormSelect {
|
|
||||||
@include flex-col;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormSelect__items {
|
|
||||||
@include flex-col;
|
|
||||||
gap: $spacing-small-3x;
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import s from './ChatFormSelect.module.scss';
|
|
||||||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
|
||||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
|
||||||
import {Radio} from '../../../../../../components/Radio';
|
|
||||||
import {SimpleButton} from '../../../SimpleButton';
|
|
||||||
|
|
||||||
export interface ChatFormSelectProps {
|
|
||||||
/**
|
|
||||||
* Дополнительный css-класс
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
registration: Partial<UseFormRegisterReturn>;
|
|
||||||
control: Control<FieldValues>;
|
|
||||||
options: string[];
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormSelect: ReactFCC<ChatFormSelectProps> = (props) => {
|
|
||||||
const {className, registration, control, options, onSubmit} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(s.ChatFormSelect, className)}>
|
|
||||||
<Controller control={control} render={({ field: { value, onChange }}) => (
|
|
||||||
<>
|
|
||||||
<div className={s.ChatFormSelect__items}>
|
|
||||||
{options.map((item, index) => (
|
|
||||||
<Radio name={registration.name} label={item} value={item} checked={item === value} onChange={(val) => onChange(val)} key={index} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SimpleButton onClick={onSubmit} />
|
|
||||||
</>
|
|
||||||
)} name={registration.name!} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormSelect';
|
|
|
@ -1,42 +0,0 @@
|
||||||
@import 'src/app/styles/vars';
|
|
||||||
|
|
||||||
$chat-input-height: 50px;
|
|
||||||
$chat-input-radius: $radius-large-x;
|
|
||||||
|
|
||||||
.ChatFormText {
|
|
||||||
@include flex-col;
|
|
||||||
gap: $spacing-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormText__richInput {
|
|
||||||
@include flex;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
min-height: $chat-input-height;
|
|
||||||
height: 100%;
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormText__input {
|
|
||||||
border-radius: $chat-input-radius 0 0 $chat-input-radius !important;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 50px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormText__richInputButton {
|
|
||||||
width: $chat-input-height !important;
|
|
||||||
height: auto !important;
|
|
||||||
border-radius: 0 $chat-input-radius $chat-input-radius 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatFormText__buttonIcon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
import { KeyboardEvent } from 'react';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
import { ReactFCC } from '../../../../../../utils/ReactFCC';
|
|
||||||
import { Textarea, TextAreaProps } from '../../../../../../components/Textarea';
|
|
||||||
import { isKey } from '../../../../../../utils/isKey';
|
|
||||||
import { Button } from '../../../../../../components/Button';
|
|
||||||
import { ReactComponent as RightIcon } from '../../../../../../assets/icons/right.svg';
|
|
||||||
import { EntityType, Hint } from '../../../../../../api/deck';
|
|
||||||
import { Hint as HintCmp, HintsContainer } from '../../../../../../components/Hint';
|
|
||||||
import { Input, InputProps } from '../../../../../../components/Input';
|
|
||||||
import s from './ChatFormText.module.scss';
|
|
||||||
|
|
||||||
export interface ChatFormTextProps {
|
|
||||||
className?: string;
|
|
||||||
onSubmit: (e: any) => void;
|
|
||||||
registration?: TextAreaProps['registration'];
|
|
||||||
setValue: (value: any) => void;
|
|
||||||
hint?: Hint | false;
|
|
||||||
type: InputProps['type'] | 'textarea';
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChatFormText: ReactFCC<ChatFormTextProps> = (props) => {
|
|
||||||
const { className, onSubmit, registration, hint, setValue, type } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={s.ChatFormText}>
|
|
||||||
<HintsContainer isLoading={hint && !hint.value}>
|
|
||||||
{hint && hint.value && (
|
|
||||||
<>
|
|
||||||
{Array.isArray(hint.value) ? (
|
|
||||||
hint.type === EntityType.cards ? (
|
|
||||||
<HintCmp onClick={() => setValue(hint.value.map((i: any) => i.name).join(', '))}>
|
|
||||||
{hint.value.map((i, index, { length: arrLength }) => (
|
|
||||||
<span title={i.description} key={index}>
|
|
||||||
{i.name}
|
|
||||||
{index !== arrLength - 1 && ', '}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</HintCmp>
|
|
||||||
) : (
|
|
||||||
hint.value.map((item: string, index: number) => (
|
|
||||||
<HintCmp onClick={() => setValue(item)} key={index}>
|
|
||||||
{item}
|
|
||||||
</HintCmp>
|
|
||||||
))
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<HintCmp onClick={() => setValue(hint.value)}>{hint.value}</HintCmp>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</HintsContainer>
|
|
||||||
|
|
||||||
<div className={clsx(s.ChatFormText__richInput, className)}>
|
|
||||||
{type === 'textarea' ? (
|
|
||||||
<Textarea
|
|
||||||
className={s.ChatFormText__input}
|
|
||||||
placeholder={'Введите сообщение'}
|
|
||||||
rows={1}
|
|
||||||
onKeyDown={(e: KeyboardEvent) => {
|
|
||||||
if (isKey(e.nativeEvent, Key.Enter)) {
|
|
||||||
e.preventDefault();
|
|
||||||
onSubmit(e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
registration={registration}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
className={s.ChatFormText__input}
|
|
||||||
type={type}
|
|
||||||
placeholder={'Введите сообщение'}
|
|
||||||
onKeyDown={(e: KeyboardEvent) => {
|
|
||||||
if (isKey(e.nativeEvent, Key.Enter)) {
|
|
||||||
e.preventDefault();
|
|
||||||
onSubmit(e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
registration={registration}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button className={s.ChatFormText__richInputButton} onClick={onSubmit}>
|
|
||||||
<RightIcon className={s.ChatFormText__buttonIcon} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatFormText';
|
|
|
@ -1,15 +0,0 @@
|
||||||
@import 'src/app/styles/vars';
|
|
||||||
|
|
||||||
.SimpleButton {
|
|
||||||
width: 100% !important;
|
|
||||||
|
|
||||||
@include media-down(tablet-small) {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.SimpleButton__text {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
column-gap: $spacing-small-3x;
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
import s from './SimpleButton.module.scss';
|
|
||||||
import {Button} from '../../../../components/Button';
|
|
||||||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
|
||||||
import {ReactComponent as RightIcon} from '../../../../assets/icons/right.svg';
|
|
||||||
|
|
||||||
export interface SimpleButtonProps {
|
|
||||||
/**
|
|
||||||
* Дополнительный css-класс
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
onClick?: (e: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SimpleButton: ReactFCC<SimpleButtonProps> = (props) => {
|
|
||||||
const {children, className, onClick} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button className={clsx(s.SimpleButton, className)} classes={{text: s.SimpleButton__text}} onClick={onClick}>
|
|
||||||
{children || 'Вперед!'}
|
|
||||||
<RightIcon className={s.ChatPage__buttonIcon} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './SimpleButton';
|
|
|
@ -1,51 +0,0 @@
|
||||||
import {useEffect, useLayoutEffect, useRef, useState} from 'react';
|
|
||||||
import {useIsDesktop} from '../../../hooks/useIsDesktop';
|
|
||||||
|
|
||||||
export const useChatUi = () => {
|
|
||||||
const backgroundRef = useRef<HTMLDivElement>(null);
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
|
||||||
const inputContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const [bgX, setBgX] = useState(50);
|
|
||||||
const [bgY, setBgY] = useState(50);
|
|
||||||
|
|
||||||
const isDesktop = useIsDesktop();
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (contentRef.current && inputContainerRef.current) {
|
|
||||||
containerRef.current?.scrollTo({ top: contentRef.current.scrollHeight });
|
|
||||||
contentRef.current.style.paddingBottom = inputContainerRef.current.scrollHeight + 'px';
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// function handler(e: MouseEvent) {
|
|
||||||
// if (isDesktop) {
|
|
||||||
// const modifierX = 17;
|
|
||||||
// const modifierY = 5;
|
|
||||||
//
|
|
||||||
// setBgX(50 + (-e.clientX / window.innerWidth) * modifierX);
|
|
||||||
// setBgY(50 + (-e.clientY / window.innerHeight) * modifierY);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// window.addEventListener('mousemove', handler);
|
|
||||||
//
|
|
||||||
// return () => window.removeEventListener('mousemove', handler);
|
|
||||||
// }, [isDesktop]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (backgroundRef.current) {
|
|
||||||
backgroundRef.current.style.backgroundPositionX = bgX + '%';
|
|
||||||
backgroundRef.current.style.backgroundPositionY = bgY + '%';
|
|
||||||
}
|
|
||||||
}, [bgX, bgY]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
backgroundRef,
|
|
||||||
containerRef,
|
|
||||||
contentRef,
|
|
||||||
inputContainerRef
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './ChatPage';
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const useInitChat = () => {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import {create} from 'zustand';
|
|
||||||
|
|
||||||
export enum ChatItemType {
|
|
||||||
send = 'send',
|
|
||||||
receive = 'receive'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ChatItem = {
|
|
||||||
type: ChatItemType;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatHistoryStore {
|
|
||||||
history: ChatItem[];
|
|
||||||
pushHistory: (item: ChatItem) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useChatHistory = create<ChatHistoryStore>((set) => ({
|
|
||||||
history: [],
|
|
||||||
pushHistory: (item: ChatItem) => set((state) => ({ history: [...state.history, item] })),
|
|
||||||
}));
|
|
|
@ -1,34 +0,0 @@
|
||||||
import {EntityType, Question} from '../../../api/deck';
|
|
||||||
|
|
||||||
export const generateAnswerFromData = (question: Question, data: any) => {
|
|
||||||
switch (question.type) {
|
|
||||||
case EntityType.text:
|
|
||||||
return data.value;
|
|
||||||
case EntityType.number:
|
|
||||||
return parseInt(data.value);
|
|
||||||
case EntityType.date:
|
|
||||||
return new Date(data.value).toISOString();
|
|
||||||
case EntityType.link:
|
|
||||||
return data.value;
|
|
||||||
case EntityType.select:
|
|
||||||
return data.value;
|
|
||||||
case EntityType.multiple_range:
|
|
||||||
return data.value;
|
|
||||||
case EntityType.multiple_date_description:
|
|
||||||
return data.value;
|
|
||||||
case EntityType.range:
|
|
||||||
return {
|
|
||||||
[question.params!.slug]: data.value,
|
|
||||||
};
|
|
||||||
case EntityType.multiple_link_description:
|
|
||||||
return data.value;
|
|
||||||
case EntityType.photo_description:
|
|
||||||
return JSON.stringify(data.value.text);
|
|
||||||
case EntityType.multiple_photo:
|
|
||||||
return '';
|
|
||||||
case EntityType.multiple_photo_description:
|
|
||||||
return JSON.stringify(data.value.map((i: any) => i.text));
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { EntityType, Question } from '../../../api/deck';
|
|
||||||
|
|
||||||
export const generateFieldsForFileAnswers = (question: Question, data: any) => {
|
|
||||||
switch (question.type) {
|
|
||||||
case EntityType.photo_description:
|
|
||||||
return {
|
|
||||||
file: data.value.file
|
|
||||||
};
|
|
||||||
case EntityType.multiple_photo:
|
|
||||||
return data.value;
|
|
||||||
case EntityType.multiple_photo_description:
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
const files: any = {};
|
|
||||||
data.value.forEach((i: any, index: number) => {
|
|
||||||
files[`file_${index + 1}`] = i.file;
|
|
||||||
});
|
|
||||||
return files;
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { Answer, EntityType } from '../../../api/deck';
|
|
||||||
import { currencyFormatter, formatDate } from '../../../utils/fomat';
|
|
||||||
import { slugsForFormat } from '../components/ChatForm/components/ChatFormMultipleRange';
|
|
||||||
|
|
||||||
export const generateTextFromAnswer = (type: EntityType, answer: Answer, files?: { [key: string]: File }) => {
|
|
||||||
switch (type) {
|
|
||||||
case EntityType.text:
|
|
||||||
return answer.answer;
|
|
||||||
case EntityType.number:
|
|
||||||
return answer.answer;
|
|
||||||
case EntityType.date:
|
|
||||||
return formatDate(answer.answer);
|
|
||||||
case EntityType.link:
|
|
||||||
return answer.answer;
|
|
||||||
case EntityType.select:
|
|
||||||
return answer.answer;
|
|
||||||
case EntityType.multiple_range:
|
|
||||||
return Object.entries(answer.answer)
|
|
||||||
.map(([key, value]: any) => (slugsForFormat.includes(key) ? currencyFormatter.format(value) : value))
|
|
||||||
.join('\n');
|
|
||||||
case EntityType.multiple_date_description:
|
|
||||||
return Object.entries(answer.answer)
|
|
||||||
.map(([key, value]) => `${formatDate(new Date(key))}: ${value}`)
|
|
||||||
.join('\n');
|
|
||||||
case EntityType.range:
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
const [slug, value]: any = Object.entries(answer.answer)[0];
|
|
||||||
return slugsForFormat.includes(slug) ? currencyFormatter.format(value) : value;
|
|
||||||
case EntityType.multiple_link_description:
|
|
||||||
return Object.entries(answer.answer)
|
|
||||||
.map(([key, value]) => `${key}: ${value}`)
|
|
||||||
.join('\n');
|
|
||||||
case EntityType.photo_description:
|
|
||||||
return `${answer.answer}\n${files?.file?.name}`;
|
|
||||||
case EntityType.multiple_photo:
|
|
||||||
return Object.values(files || {}).map((file) => `${file.name}`);
|
|
||||||
case EntityType.multiple_photo_description:
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
let result = '';
|
|
||||||
answer.answer.forEach((desc: string, index: number, arr: any) => {
|
|
||||||
result += files?.[`file_${index + 1}`].name + '\n';
|
|
||||||
result += desc + (index !== arr.length - 1 ? '\n\n' : '');
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -30,7 +30,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.TextItem__name {
|
.TextItem__name {
|
||||||
//font-size: $font-size-18;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: $font-weight-600;
|
font-weight: $font-weight-600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -40,3 +39,7 @@
|
||||||
margin-top: $spacing-small-3x;
|
margin-top: $spacing-small-3x;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.TextItem__text600 {
|
||||||
|
font-weight: $font-weight-600;
|
||||||
|
}
|
|
@ -68,7 +68,7 @@ export const TextItem: ReactFCC<TextItemProps> = (props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={s.TextItem__row}>
|
<div className={s.TextItem__row}>
|
||||||
<Text component={'span'} variant={ETextVariants.PROGRAMMING_CODE_REGULAR}>
|
<Text className={s.TextItem__text600} component={'span'} variant={ETextVariants.PROGRAMMING_CODE_REGULAR}>
|
||||||
Итоговая оценка: {text.score.total as unknown as string}
|
Итоговая оценка: {text.score.total as unknown as string}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user