mirror of
https://github.com/magnum-opus-nn-cp/frontend.git
synced 2025-10-18 01:24:14 +03:00
215 lines
6.9 KiB
TypeScript
215 lines
6.9 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||
import { useDropzone } from 'react-dropzone';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import clsx from 'clsx';
|
||
import { ReactFCC } from '../../utils/ReactFCC';
|
||
import { Heading, HeadingSize } from '../../components/Heading';
|
||
import { ETextVariants, Text } from '../../components/Text';
|
||
import { Button, ButtonSize, ButtonVariant } from '../../components/Button';
|
||
import { Divider } from '../../components/Divider';
|
||
import { Textarea } from '../../components/Textarea';
|
||
import { useCreateProcess } from '../../api/process';
|
||
import { useProcess } from '../../api/process/getProcess';
|
||
import { useSingleTimeout } from '../../hooks/useSingleTimeout';
|
||
import { Upload } from '../../components/Upload';
|
||
import { Attachment } from '../../components/Attachment';
|
||
import { Loader } from '../../components/Loader';
|
||
import { PathBuilder } from '../../app/routes';
|
||
import { ReactComponent as PlusIcon } from './assets/plus.svg';
|
||
import s from './HomePage.module.scss';
|
||
|
||
export type FormFields = {
|
||
text?: string;
|
||
files: File[];
|
||
};
|
||
|
||
export const PROCESS_POLLING_MS = 500;
|
||
|
||
export const HomePage: ReactFCC = () => {
|
||
const {
|
||
register,
|
||
handleSubmit,
|
||
watch,
|
||
setValue,
|
||
formState: { errors }
|
||
} = useForm<FormFields>({
|
||
defaultValues: {
|
||
files: []
|
||
}
|
||
});
|
||
|
||
// const [processId, setProcessId] = useState<string | null>(null);
|
||
|
||
const {
|
||
data: createProcessResponse,
|
||
mutateAsync: createProcess,
|
||
isLoading: createProcessLoading
|
||
} = useCreateProcess();
|
||
const processId = createProcessResponse?.id;
|
||
|
||
const {
|
||
data: process,
|
||
refetch: refetchProcess,
|
||
isFetching: processFetching
|
||
} = useProcess({
|
||
processId: processId || '',
|
||
config: {
|
||
enabled: !!processId
|
||
}
|
||
});
|
||
|
||
const timeout = useSingleTimeout();
|
||
|
||
const onSubmit: SubmitHandler<FormFields> = useCallback(
|
||
async (data) => {
|
||
await createProcess({
|
||
text: data.text,
|
||
files: data.files
|
||
});
|
||
},
|
||
[createProcess]
|
||
);
|
||
|
||
useEffect(() => {
|
||
if (processId) {
|
||
const startPolling = () => {
|
||
timeout.set(async () => {
|
||
const { data: process } = await refetchProcess();
|
||
if (process && process.current < process.total) {
|
||
startPolling();
|
||
}
|
||
}, PROCESS_POLLING_MS);
|
||
};
|
||
|
||
if (processId) {
|
||
startPolling();
|
||
}
|
||
}
|
||
}, [processId, refetchProcess, timeout]);
|
||
|
||
const navigate = useNavigate();
|
||
|
||
useEffect(() => {
|
||
if (processId && process && process.current === process.total) {
|
||
navigate(PathBuilder.getProcessPath(processId));
|
||
}
|
||
}, [navigate, process, processId]);
|
||
|
||
// ------ Обработка DnD ------
|
||
|
||
const currentText = watch('text');
|
||
const currentFiles = watch('files');
|
||
|
||
const onDrop = useCallback(
|
||
(acceptedFiles: File[]) => {
|
||
setValue('text', '');
|
||
setValue('files', [...currentFiles, ...acceptedFiles]);
|
||
},
|
||
[currentFiles, setValue]
|
||
);
|
||
|
||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||
onDrop,
|
||
noClick: true
|
||
// todo подумать насчет валидации на фронте (нужна ли?)
|
||
// accept: {
|
||
// 'application/msword': ['.doc'],
|
||
// 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
|
||
// 'application/vnd.ms-excel': ['.xls'],
|
||
// 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx']
|
||
// }
|
||
});
|
||
|
||
// ------ Логика UI ------
|
||
|
||
const isLoading =
|
||
createProcessLoading || !!processId || processFetching || !!(process && process.current < process.total);
|
||
const isDisabled = !currentText && currentFiles.length === 0;
|
||
|
||
return (
|
||
<div className={s.HomePage}>
|
||
{!isLoading ? (
|
||
<div className={s.HomePage__main}>
|
||
<Heading size={HeadingSize.H2} className={s.HomePage__title}>
|
||
Анализ текстовых пресс-релизов
|
||
</Heading>
|
||
|
||
<Text className={s.HomePage__text} variant={ETextVariants.BODY_M_REGULAR}>
|
||
Позволяет оценить кредитный рейтинг компании на основе пресс-релиза с выделением в тексте меток по различным
|
||
метрикам.
|
||
</Text>
|
||
|
||
<form className={s.HomePage__box} onSubmit={handleSubmit(onSubmit)}>
|
||
<div
|
||
className={clsx(s.HomePage__dropBox, {
|
||
[s.HomePage__dropBox_hidden]: isDragActive
|
||
})}
|
||
{...getRootProps()}>
|
||
{currentFiles.length === 0 ? (
|
||
<Textarea
|
||
className={s.HomePage__textarea}
|
||
classes={{
|
||
input: s.HomePage__textareaInput
|
||
}}
|
||
registration={register('text')}
|
||
rows={8}
|
||
placeholder={'Текст пресс-релиза...'}
|
||
error={!!errors.text}
|
||
/>
|
||
) : (
|
||
<div className={s.HomePage__filesContainer}>
|
||
{currentFiles.map((item, index) => (
|
||
<Attachment
|
||
file={item}
|
||
onClick={() => setValue('files', [...currentFiles.filter((i) => i !== item)])}
|
||
key={index}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<Text className={s.HomePage__uploadHint} variant={ETextVariants.CAPTION_S_REGULAR}>
|
||
Загрузите файлы, перетащив их мышкой или нажав кнопку ниже <br />
|
||
Доступны файлы Word, Excel, PDF, TXT, изображения
|
||
</Text>
|
||
|
||
<Upload {...getInputProps()}>
|
||
<Button
|
||
component={'div'}
|
||
className={s.HomePage__uploadButton}
|
||
variant={ButtonVariant.secondary}
|
||
size={ButtonSize.small_x}>
|
||
Загрузить файлы
|
||
</Button>
|
||
</Upload>
|
||
|
||
<div className={s.HomePage__dropBoxPlaceholder}>
|
||
<PlusIcon className={s.HomePage__dropBoxPlaceholderIcon} />
|
||
</div>
|
||
</div>
|
||
|
||
<Divider className={s.HomePage__divider} />
|
||
|
||
<Button
|
||
type={'submit'}
|
||
className={s.HomePage__submitButton}
|
||
size={ButtonSize.large_x}
|
||
isLoading={isLoading}
|
||
disabled={isDisabled}>
|
||
Отправить
|
||
</Button>
|
||
</form>
|
||
</div>
|
||
) : (
|
||
<div className={s.HomePage__loaderContainer}>
|
||
<Loader className={s.HomePage__loader} />
|
||
<div className={s.HomePage__loaderText}>
|
||
{process?.current ?? 0}/{process?.total ?? 0}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|