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'; import { QUERY_KEY_PROCESSES } from './queryKeys';
export type CreateProcessDTO = Partial<Pick<TextDescriptor, 'text'>> & { export type CreateProcessDTO = Partial<Pick<TextDescriptor, 'text'>> & {
files?: []; files?: File[];
}; };
export type CreateProcessResponse = { export type CreateProcessResponse = {
@ -14,7 +14,33 @@ export type CreateProcessResponse = {
}; };
export const createProcess = (data: CreateProcessDTO): Promise<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 = { type UseCreateProcessOptions = {

View File

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

View File

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

View File

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

View File

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

View File

@ -2,13 +2,19 @@
.DefaultLayout { .DefaultLayout {
@include grid-for(desktop-small); @include grid-for(desktop-small);
min-height: 100vh;
@include tablet-down { @include tablet-down {
@include grid-for(tablet-large); @include grid-for(tablet-large);
} }
@include mobile-down {
@include grid-for(mobile-small);
}
} }
.DefaultLayout__container { .DefaultLayout__container {
@include flex-col;
grid-column: 3 / span 8; grid-column: 3 / span 8;
@include tablet-down { @include tablet-down {
@ -17,14 +23,27 @@
} }
.DefaultLayout__header { .DefaultLayout__header {
@include flex-middle; display: grid;
column-gap: $spacing-medium; gap: $spacing-small-4x $spacing-medium;
height: $header-height;
margin-bottom: $spacing-medium-x; 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 { .DefaultLayout__logo {
width: auto; width: fit-content;
height: 40px; height: 40px;
} }
@ -33,8 +52,33 @@
background-color: $color-accent; background-color: $color-accent;
padding: $spacing-small-2x; padding: $spacing-small-2x;
margin-right: auto; 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 { .DefaultLayout__logo_cbr {
height: 44px; height: 44px;
grid-area: cbr;
@include mobile-down {
height: 28px;
}
}
.DefaultLayout__content {
height: 100%;
} }

View File

@ -3,7 +3,9 @@
$textarea-height: 192px; $textarea-height: 192px;
.HomePage { .HomePage {
@include flex-col;
padding-bottom: $spacing-medium-x; padding-bottom: $spacing-medium-x;
height: 100%;
} }
.HomePage__title { .HomePage__title {
@ -22,6 +24,11 @@ $textarea-height: 192px;
background-color: $color-background-dark-100; background-color: $color-background-dark-100;
border-radius: $radius-large; border-radius: $radius-large;
padding: $spacing-medium; padding: $spacing-medium;
@include mobile-down {
padding: 0;
background-color: transparent;
}
} }
.HomePage__dropBox { .HomePage__dropBox {
@ -54,8 +61,10 @@ $textarea-height: 192px;
} }
} }
.HomePage__button { .HomePage__submitButton {
margin-left: auto; @include mobile-up {
margin-left: auto;
}
} }
.HomePage__buttonHint { .HomePage__buttonHint {
@ -80,17 +89,18 @@ $textarea-height: 192px;
border-radius: $radius-small; border-radius: $radius-small;
background-color: $color-background-primary; background-color: $color-background-primary;
padding: $spacing-small-x; padding: $spacing-small-x;
@include mobile-down {
justify-content: center;
}
} }
.HomePage__uploadContainer { .HomePage__uploadContainer {
width: fit-content; width: fit-content;
//padding-top: $spacing-small-3x;
//border-top: 1px dashed $color-divider-darker;
} }
.HomePage__uploadButton { .HomePage__uploadButton {
margin-right: auto; margin-right: auto;
//margin-bottom: $spacing-small-4x;
} }
.HomePage__uploadHint { .HomePage__uploadHint {
@ -101,6 +111,21 @@ $textarea-height: 192px;
.HomePage__orHint { .HomePage__orHint {
margin-bottom: $spacing-small-3x; margin-bottom: $spacing-small-3x;
letter-spacing: 0.06rem; 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 { useSingleTimeout } from '../../hooks/useSingleTimeout';
import { Upload } from '../../components/Upload'; import { Upload } from '../../components/Upload';
import { Attachment } from '../../components/Attachment'; import { Attachment } from '../../components/Attachment';
import { Loader } from '../../components/Loader';
import { ReactComponent as PlusIcon } from './assets/plus.svg'; import { ReactComponent as PlusIcon } from './assets/plus.svg';
import s from './HomePage.module.scss'; import s from './HomePage.module.scss';
@ -38,21 +39,26 @@ export const HomePage: ReactFCC = () => {
const [processId, setProcessId] = useState<string | null>(null); const [processId, setProcessId] = useState<string | null>(null);
const { data: process, refetch: refetchProcess } = useProcess({ const {
data: process,
refetch: refetchProcess,
isFetching: processFetching
} = useProcess({
processId: processId || '', processId: processId || '',
config: { config: {
enabled: !!processId enabled: !!processId
} }
}); });
const { mutateAsync: createProcess, isLoading } = useCreateProcess(); const { mutateAsync: createProcess, isLoading: createProcessLoading } = useCreateProcess();
const timeout = useSingleTimeout(); const timeout = useSingleTimeout();
const onSubmit: SubmitHandler<FormFields> = useCallback( const onSubmit: SubmitHandler<FormFields> = useCallback(
async (data) => { async (data) => {
const response = await createProcess({ const response = await createProcess({
text: data.text text: data.text,
files: data.files
}); });
setProcessId(response.id); setProcessId(response.id);
@ -65,7 +71,7 @@ export const HomePage: ReactFCC = () => {
const startPolling = () => { const startPolling = () => {
timeout.set(async () => { timeout.set(async () => {
const { data: process } = await refetchProcess(); const { data: process } = await refetchProcess();
if (process && process.done < process.count) { if (process && process.current < process.total) {
startPolling(); startPolling();
} }
}, PROCESS_POLLING_MS); }, PROCESS_POLLING_MS);
@ -77,13 +83,6 @@ export const HomePage: ReactFCC = () => {
} }
}, [processId, refetchProcess, timeout]); }, [processId, refetchProcess, timeout]);
// todo it's mock!
useEffect(() => {
if (process && process.done === process.count) {
alert(`Кредитный рейтинг ${process.texts[0].score}`);
}
}, [process]);
// ------ Обработка DnD ------ // ------ Обработка DnD ------
const currentText = watch('text'); const currentText = watch('text');
@ -110,88 +109,88 @@ export const HomePage: ReactFCC = () => {
// ------ Логика UI ------ // ------ Логика UI ------
const isLoading = createProcessLoading || processFetching || !!(process && process.current < process.total);
const isDisabled = !currentText && currentFiles.length === 0; const isDisabled = !currentText && currentFiles.length === 0;
return ( return (
<div className={s.HomePage}> <div className={s.HomePage}>
<Heading size={HeadingSize.H2} className={s.HomePage__title}> {!isLoading ? (
Анализ текстовых пресс-релизов <div className={s.HomePage__main}>
</Heading> <Heading size={HeadingSize.H2} className={s.HomePage__title}>
Анализ текстовых пресс-релизов
</Heading>
<Text className={s.HomePage__text} variant={ETextVariants.BODY_M_REGULAR}> <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> </Text>
<Upload {...getInputProps()}> <form className={s.HomePage__box} onSubmit={handleSubmit(onSubmit)}>
<Button <div
component={'div'} className={clsx(s.HomePage__dropBox, {
className={s.HomePage__uploadButton} [s.HomePage__dropBox_hidden]: isDragActive
variant={ButtonVariant.secondary} })}
size={ButtonSize.small_x}> {...getRootProps()}>
Загрузить файлы {currentFiles.length === 0 ? (
</Button> <Textarea
</Upload> 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}> <Text className={s.HomePage__uploadHint} variant={ETextVariants.CAPTION_S_REGULAR}>
<PlusIcon className={s.HomePage__dropBoxPlaceholderIcon} /> Загрузите файлы, перетащив их мышкой или нажав кнопку ниже <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> </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> </div>
); );
}; };