feat(home): add file processing loader

This commit is contained in:
Pavel Torbeev 2023-09-09 01:37:46 +03:00
parent b7b15fd26b
commit acaf7d0b5e
8 changed files with 199 additions and 118 deletions

View File

@ -6,7 +6,7 @@ import { PROCESS_API_URL } from './urlKeys';
import { QUERY_KEY_PROCESSES } from './queryKeys';
export type CreateProcessDTO = Partial<Pick<TextDescriptor, 'text'>> & {
files?: [];
files?: File[];
};
export type CreateProcessResponse = {
@ -14,7 +14,33 @@ export type CreateProcessResponse = {
};
export const createProcess = (data: CreateProcessDTO): Promise<CreateProcessResponse> => {
return axios.post(`${PROCESS_API_URL}/`, data);
const isForm = data.files?.length !== 0;
console.log(data);
let inputData: any;
if (isForm) {
inputData = new FormData();
if (data.text) {
inputData.append('text', `${data.text}`);
}
data.files?.forEach((file, index) => {
inputData.append(`file_${index + 1}`, file);
});
} else {
inputData = {
text: data.text
};
}
return axios.post(`${PROCESS_API_URL}/`, inputData, {
headers: isForm
? {
'Content-Type': 'multipart/form-data'
}
: {}
});
};
type UseCreateProcessOptions = {

View File

@ -5,6 +5,6 @@ export type TextDescriptor = {
export type ProcessDescriptor = {
texts: TextDescriptor[];
done: number;
count: number;
current: number;
total: number;
};

View File

@ -3,6 +3,7 @@
.Attachment {
@include flex-col-middle;
cursor: pointer;
width: 100px;
}
.Attachment__text {

View File

@ -1,16 +1,17 @@
@import 'src/app/styles/vars';
.Loader {
--loader-size: 20px;
display: flex;
align-items: center;
width: 20px;
height: 20px;
width: var(--loader-size);
height: var(--loader-size);
color: $color-on-surface-dark-100;
}
.LoaderIcon {
width: 100%;
height: 100%;
color: $color-on-surface-dark-100;
animation: spinner 0.7s linear infinite;
}

View File

@ -1,22 +1,7 @@
import { memo } from 'react';
import clsx from 'clsx';
import s from './Loader.module.scss';
import { ReactComponent as LoaderIcon } from '../../assets/icons/loader.svg';
// export enum LoaderSize {
// /**
// * Размер лоадера 20х20
// */
// small = 'small',
// /**
// * Размер лоадера 30х30
// */
// medium = 'medium',
// /**
// * Размер лоадера 40х40
// */
// large = 'large'
// }
import s from './Loader.module.scss';
export interface LoaderProps {
className?: string;
@ -27,7 +12,7 @@ export const Loader = memo((props: LoaderProps) => {
return (
<div className={clsx(s.Loader, className)}>
<LoaderIcon className={clsx(s.LoaderIcon)} />
<LoaderIcon className={clsx(s.LoaderIcon)} />
</div>
);
});

View File

@ -2,13 +2,19 @@
.DefaultLayout {
@include grid-for(desktop-small);
min-height: 100vh;
@include tablet-down {
@include grid-for(tablet-large);
}
@include mobile-down {
@include grid-for(mobile-small);
}
}
.DefaultLayout__container {
@include flex-col;
grid-column: 3 / span 8;
@include tablet-down {
@ -17,14 +23,27 @@
}
.DefaultLayout__header {
@include flex-middle;
column-gap: $spacing-medium;
height: $header-height;
display: grid;
gap: $spacing-small-4x $spacing-medium;
margin-bottom: $spacing-medium-x;
@include mobile-up {
grid-template: 'mopus cp cbr'/ auto 1fr auto;
height: $header-height;
align-items: center;
}
@include mobile-down {
grid-template:
'mopus cp'
'mopus cbr'
/ 1fr auto;
align-items: center;
}
}
.DefaultLayout__logo {
width: auto;
width: fit-content;
height: 40px;
}
@ -33,8 +52,33 @@
background-color: $color-accent;
padding: $spacing-small-2x;
margin-right: auto;
grid-area: mopus;
height: $header-height;
width: fit-content;
}
.DefaultLayout__logo_cp {
grid-area: cp;
@include mobile-up {
justify-self: end;
}
@include mobile-down {
height: 24px;
align-self: end;
}
}
.DefaultLayout__logo_cbr {
height: 44px;
grid-area: cbr;
@include mobile-down {
height: 28px;
}
}
.DefaultLayout__content {
height: 100%;
}

View File

@ -3,7 +3,9 @@
$textarea-height: 192px;
.HomePage {
@include flex-col;
padding-bottom: $spacing-medium-x;
height: 100%;
}
.HomePage__title {
@ -22,6 +24,11 @@ $textarea-height: 192px;
background-color: $color-background-dark-100;
border-radius: $radius-large;
padding: $spacing-medium;
@include mobile-down {
padding: 0;
background-color: transparent;
}
}
.HomePage__dropBox {
@ -54,8 +61,10 @@ $textarea-height: 192px;
}
}
.HomePage__button {
margin-left: auto;
.HomePage__submitButton {
@include mobile-up {
margin-left: auto;
}
}
.HomePage__buttonHint {
@ -80,17 +89,18 @@ $textarea-height: 192px;
border-radius: $radius-small;
background-color: $color-background-primary;
padding: $spacing-small-x;
@include mobile-down {
justify-content: center;
}
}
.HomePage__uploadContainer {
width: fit-content;
//padding-top: $spacing-small-3x;
//border-top: 1px dashed $color-divider-darker;
}
.HomePage__uploadButton {
margin-right: auto;
//margin-bottom: $spacing-small-4x;
}
.HomePage__uploadHint {
@ -101,6 +111,21 @@ $textarea-height: 192px;
.HomePage__orHint {
margin-bottom: $spacing-small-3x;
letter-spacing: 0.06rem;
//color: $color-text-tertiary;
//margin-left: 70px;
}
.HomePage__loaderContainer {
@include flex-col-middle;
justify-content: center;
row-gap: $spacing-small-4x;
height: 100%;
}
.HomePage__loader {
--loader-size: 96px;
color: $color-background-secondary !important;
}
.HomePage__loaderText {
@include text-programming-code-regular;
font-size: $font-size-16;
}

View File

@ -13,6 +13,7 @@ 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 { ReactComponent as PlusIcon } from './assets/plus.svg';
import s from './HomePage.module.scss';
@ -38,21 +39,26 @@ export const HomePage: ReactFCC = () => {
const [processId, setProcessId] = useState<string | null>(null);
const { data: process, refetch: refetchProcess } = useProcess({
const {
data: process,
refetch: refetchProcess,
isFetching: processFetching
} = useProcess({
processId: processId || '',
config: {
enabled: !!processId
}
});
const { mutateAsync: createProcess, isLoading } = useCreateProcess();
const { mutateAsync: createProcess, isLoading: createProcessLoading } = useCreateProcess();
const timeout = useSingleTimeout();
const onSubmit: SubmitHandler<FormFields> = useCallback(
async (data) => {
const response = await createProcess({
text: data.text
text: data.text,
files: data.files
});
setProcessId(response.id);
@ -65,7 +71,7 @@ export const HomePage: ReactFCC = () => {
const startPolling = () => {
timeout.set(async () => {
const { data: process } = await refetchProcess();
if (process && process.done < process.count) {
if (process && process.current < process.total) {
startPolling();
}
}, PROCESS_POLLING_MS);
@ -77,13 +83,6 @@ export const HomePage: ReactFCC = () => {
}
}, [processId, refetchProcess, timeout]);
// todo it's mock!
useEffect(() => {
if (process && process.done === process.count) {
alert(`Кредитный рейтинг ${process.texts[0].score}`);
}
}, [process]);
// ------ Обработка DnD ------
const currentText = watch('text');
@ -110,88 +109,88 @@ export const HomePage: ReactFCC = () => {
// ------ Логика UI ------
const isLoading = createProcessLoading || processFetching || !!(process && process.current < process.total);
const isDisabled = !currentText && currentFiles.length === 0;
return (
<div className={s.HomePage}>
<Heading size={HeadingSize.H2} className={s.HomePage__title}>
Анализ текстовых пресс-релизов
</Heading>
{!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}
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 className={s.HomePage__text} variant={ETextVariants.BODY_M_REGULAR}>
Позволяет оценить кредитный рейтинг компании на основе пресс-релиза с выделением в тексте меток по различным
метрикам.
</Text>
<Upload {...getInputProps()}>
<Button
component={'div'}
className={s.HomePage__uploadButton}
variant={ButtonVariant.secondary}
size={ButtonSize.small_x}>
Загрузить файлы
</Button>
</Upload>
<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}
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>
)}
<div className={s.HomePage__dropBoxPlaceholder}>
<PlusIcon className={s.HomePage__dropBoxPlaceholderIcon} />
<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>
{/*<Text className={s.HomePage__orHint} variant={ETextVariants.PROGRAMMING_CODE_MEDIUM}>*/}
{/* ИЛИ*/}
{/*</Text>*/}
{/*<Button className={s.HomePage__button} size={ButtonSize.large}>*/}
{/* Выбрать DOCX/PDF файл*/}
{/*</Button>*/}
{/*<Text className={s.HomePage__buttonHint} variant={ETextVariants.BODY_S_REGULAR}>*/}
{/* Или перетащите файл мышкой*/}
{/*</Text>*/}
<Divider className={s.HomePage__divider} />
<Button
type={'submit'}
className={s.HomePage__button}
size={ButtonSize.large_x}
isLoading={isLoading}
disabled={isDisabled}>
Отправить
</Button>
</form>
)}
</div>
);
};