mirror of
https://github.com/magnum-opus-nn-cp/frontend.git
synced 2024-11-21 17:16:36 +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 { ChatPage } from '../../pages/chat';
|
||||
import { HomePage } from '../../pages/home';
|
||||
import { DefaultLayout } from '../../pages/_layouts/DefaultLayout';
|
||||
import { TextPage } from '../../pages/text';
|
||||
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 = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<DefaultLayout />}>
|
||||
<Route path={CHAT_PAGE_ROUTE} element={<ChatPage />} />
|
||||
<Route path={HOME_PAGE_ROUTE} element={<HomePage />} />
|
||||
<Route path={RESPONSE_PAGE_ROUTE} element={<ResponsePage />} />
|
||||
<Route path={TEXT_PAGE_ROUTE} element={<TextPage />} />
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export const CHAT_PAGE_ROUTE = `/chat`;
|
||||
export const HOME_PAGE_ROUTE = `/`;
|
||||
|
||||
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.22.4:8000';
|
||||
// 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_URL = 'http://192.168.107.4';
|
||||
export const BACKEND_URL = 'https://4810-176-59-122-187.ngrok-free.app';
|
||||
export const BACKEND_MEDIA_PORT = '8000';
|
||||
|
||||
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 {
|
||||
//font-size: $font-size-18;
|
||||
text-transform: uppercase;
|
||||
font-weight: $font-weight-600;
|
||||
white-space: nowrap;
|
||||
|
@ -40,3 +39,7 @@
|
|||
margin-top: $spacing-small-3x;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.TextItem__text600 {
|
||||
font-weight: $font-weight-600;
|
||||
}
|
|
@ -68,7 +68,7 @@ export const TextItem: ReactFCC<TextItemProps> = (props) => {
|
|||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user