mirror of
https://github.com/magnum-opus-nn-cp/frontend.git
synced 2024-11-21 17:16:36 +03:00
feat: add home page (text & files uploading)
This commit is contained in:
parent
07ea67ec81
commit
b7b15fd26b
|
@ -32,6 +32,7 @@
|
|||
"prettier": "^3.0.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hook-form": "^7.45.4",
|
||||
"react-router-dom": "^6.15.0",
|
||||
|
|
32
src/api/process/createProcess.ts
Normal file
32
src/api/process/createProcess.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { useMutation } from '@tanstack/react-query';
|
||||
import { MutationConfig, queryClient } from '../../lib/react-query';
|
||||
import { axios } from '../../lib/axios';
|
||||
import { TextDescriptor } from './types';
|
||||
import { PROCESS_API_URL } from './urlKeys';
|
||||
import { QUERY_KEY_PROCESSES } from './queryKeys';
|
||||
|
||||
export type CreateProcessDTO = Partial<Pick<TextDescriptor, 'text'>> & {
|
||||
files?: [];
|
||||
};
|
||||
|
||||
export type CreateProcessResponse = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const createProcess = (data: CreateProcessDTO): Promise<CreateProcessResponse> => {
|
||||
return axios.post(`${PROCESS_API_URL}/`, data);
|
||||
};
|
||||
|
||||
type UseCreateProcessOptions = {
|
||||
config?: MutationConfig<typeof createProcess>;
|
||||
};
|
||||
|
||||
export const useCreateProcess = ({ config }: UseCreateProcessOptions = {}) => {
|
||||
return useMutation({
|
||||
onMutate: async () => {
|
||||
await queryClient.cancelQueries([QUERY_KEY_PROCESSES]);
|
||||
},
|
||||
...config,
|
||||
mutationFn: createProcess
|
||||
});
|
||||
};
|
29
src/api/process/getProcess.ts
Normal file
29
src/api/process/getProcess.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { axios } from '../../lib/axios';
|
||||
import { ExtractFnReturnType, QueryConfig } from '../../lib/react-query';
|
||||
import { ProcessDescriptor } from './types';
|
||||
import { PROCESS_API_URL } from './urlKeys';
|
||||
import { QUERY_KEY_PROCESSES } from './queryKeys';
|
||||
|
||||
export type GetProcessResponse = ProcessDescriptor;
|
||||
|
||||
export const getProcess = ({ processId }: { processId: string }): Promise<GetProcessResponse> => {
|
||||
return axios.get(`${PROCESS_API_URL}/${processId}`);
|
||||
};
|
||||
|
||||
type QueryFnType = typeof getProcess;
|
||||
|
||||
type UseProcessOptions = {
|
||||
processId: string;
|
||||
config?: QueryConfig<QueryFnType>;
|
||||
};
|
||||
|
||||
export const useProcess = ({ processId, config }: UseProcessOptions) => {
|
||||
return useQuery<ExtractFnReturnType<QueryFnType>>({
|
||||
...config,
|
||||
queryKey: [QUERY_KEY_PROCESSES, processId],
|
||||
queryFn: async () => {
|
||||
return await getProcess({ processId });
|
||||
}
|
||||
});
|
||||
};
|
2
src/api/process/index.ts
Normal file
2
src/api/process/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './types';
|
||||
export * from './createProcess';
|
1
src/api/process/queryKeys.ts
Normal file
1
src/api/process/queryKeys.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const QUERY_KEY_PROCESSES = 'processes';
|
10
src/api/process/types.ts
Normal file
10
src/api/process/types.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export type TextDescriptor = {
|
||||
score: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type ProcessDescriptor = {
|
||||
texts: TextDescriptor[];
|
||||
done: number;
|
||||
count: number;
|
||||
};
|
2
src/api/process/urlKeys.ts
Normal file
2
src/api/process/urlKeys.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export const PROCESS_API_URL = '/process';
|
||||
export const PROCESS_PARAM = 'processId';
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
// синий
|
||||
$color-text: #010819;
|
||||
$color-background: #e6edfe;
|
||||
//$color-background: #e6edfe;
|
||||
$color-background: #fafafa;
|
||||
$color-primary: #0647e0;
|
||||
$color-secondary: #cddcfe;
|
||||
$color-accent: #064ff9;
|
||||
|
@ -30,24 +31,42 @@ $color-accent: #064ff9;
|
|||
|
||||
$color-background-primary: $color-background;
|
||||
$color-background-secondary: $color-secondary;
|
||||
$color-background-dark-100: darken($color-background, 1%);
|
||||
$color-background-dark-200: darken($color-background, 2%);
|
||||
$color-background-dark-300: darken($color-background, 3%);
|
||||
$color-background-dark-400: darken($color-background, 4%);
|
||||
$color-background-dark-100: darken($color-background, 3%);
|
||||
$color-background-dark-200: darken($color-background, 6%);
|
||||
$color-background-dark-300: darken($color-background, 12%);
|
||||
$color-background-dark-400: darken($color-background, 24%);
|
||||
$color-background-dark-500: darken($color-background, 48%);
|
||||
$color-background-accent: $color-accent;
|
||||
|
||||
$color-text-primary: $color-text;
|
||||
$color-text-secondary: color.adjust($color-background, $blackness: 100%);
|
||||
$color-text-tertiary: color.adjust($color-background, $blackness: 70%);
|
||||
$color-text-secondary: darken($color-background, 40%);
|
||||
$color-text-tertiary: darken($color-background, 60%);
|
||||
$color-text-quaternary: $color-background;
|
||||
$color-text-brand: $color-primary;
|
||||
$color-text-disabled: color.adjust($color-text, $lightness: 40%);
|
||||
|
||||
$color-brand-primary: $color-primary;
|
||||
$color-brand-hover: color.adjust($color-primary, $blackness: 2%);
|
||||
$color-brand-disabled: color.adjust($color-primary, $lightness: 2%);
|
||||
$color-brand-hover: darken($color-brand-primary, 3%);
|
||||
$color-brand-active: darken($color-brand-primary, 6%);
|
||||
$color-brand-disabled: color.adjust($color-brand-primary, $lightness: 30%);
|
||||
|
||||
$color-border: color.invert($color-background);
|
||||
$color-border-default: $color-background-dark-300;
|
||||
$color-border-hover: lighten($color-brand-primary, 30%);
|
||||
$color-border-active: $color-brand-primary;
|
||||
|
||||
$color-divider-dark: $color-border-default;
|
||||
$color-divider-darker: darken($color-border-default, 10%);
|
||||
|
||||
// Button
|
||||
$color-button-primary-default-fill: $color-brand-primary;
|
||||
$color-button-primary-hover-fill: $color-brand-hover;
|
||||
$color-button-primary-pressed-fill: $color-brand-active;
|
||||
$color-button-primary-disabled-fill: $color-brand-disabled;
|
||||
|
||||
$color-button-secondary-default-fill: $color-background-secondary;
|
||||
$color-button-secondary-hover-fill: darken($color-secondary, 3%);
|
||||
$color-button-secondary-pressed-fill: darken($color-secondary, 6%);
|
||||
$color-button-secondary-disabled-fill: lighten($color-secondary, 3%);
|
||||
|
||||
//@debug $color-border;
|
||||
|
||||
|
|
32
src/components/Attachment/Attachment.module.scss
Normal file
32
src/components/Attachment/Attachment.module.scss
Normal file
|
@ -0,0 +1,32 @@
|
|||
@import 'src/app/styles/vars';
|
||||
|
||||
.Attachment {
|
||||
@include flex-col-middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Attachment__text {
|
||||
@include text-programming-code-regular;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.Attachment__text_hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.Attachment__text_x {
|
||||
top: -$line-height-16 / 2;
|
||||
font-size: 16px;
|
||||
line-height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.Attachment__icon {
|
||||
width: 64px;
|
||||
height: 88px;
|
||||
margin-bottom: $spacing-small-4x;
|
||||
}
|
59
src/components/Attachment/Attachment.tsx
Normal file
59
src/components/Attachment/Attachment.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { ComponentType, ElementType, ReactComponentElement } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { ReactFCC } from '../../utils/ReactFCC';
|
||||
import { useHover } from '../../hooks/useHover';
|
||||
import { ReactComponent as DocIcon } from './assets/doc.svg';
|
||||
import { ReactComponent as XlsIcon } from './assets/xls.svg';
|
||||
import { ReactComponent as PdfIcon } from './assets/pdf.svg';
|
||||
import { ReactComponent as FileIcon } from './assets/file.svg';
|
||||
import s from './Attachment.module.scss';
|
||||
|
||||
export interface AttachmentProps {
|
||||
/**
|
||||
* Дополнительный css-класс
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Объект файла
|
||||
*/
|
||||
file: File;
|
||||
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const extensionIcon: { [key: string]: ElementType } = {
|
||||
doc: DocIcon,
|
||||
docx: DocIcon,
|
||||
xls: XlsIcon,
|
||||
xlsx: XlsIcon,
|
||||
pdf: PdfIcon
|
||||
};
|
||||
|
||||
export const Attachment: ReactFCC<AttachmentProps> = (props) => {
|
||||
const { className, file, onClick } = props;
|
||||
|
||||
const ext = file.name.split('.')[file.name.split('.').length - 1];
|
||||
const Component = extensionIcon[ext] || FileIcon;
|
||||
|
||||
const { hovered, ...hoverProps } = useHover();
|
||||
|
||||
return (
|
||||
<div className={clsx(s.Attachment, className)} onClick={() => onClick?.()} {...hoverProps}>
|
||||
<Component className={s.Attachment__icon} />
|
||||
|
||||
<div
|
||||
className={clsx(s.Attachment__text, {
|
||||
[s.Attachment__text_hidden]: hovered
|
||||
})}>
|
||||
{file.name}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx(s.Attachment__text, s.Attachment__text_x, {
|
||||
[s.Attachment__text_hidden]: !hovered
|
||||
})}>
|
||||
{'×'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
7
src/components/Attachment/assets/doc.svg
Normal file
7
src/components/Attachment/assets/doc.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg width="64" height="88" viewBox="0 0 64 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 2C0.5 1.17157 1.17157 0.5 2 0.5H46.1855C46.5771 0.5 46.9532 0.653173 47.2334 0.926789L63.048 16.3692C63.337 16.6515 63.5 17.0384 63.5 17.4424V86C63.5 86.8284 62.8284 87.5 62 87.5H2C1.17157 87.5 0.5 86.8284 0.5 86V2Z" fill="#EFF8FF" stroke="#CFEBFF"/>
|
||||
<path d="M46 17V0H48.1716C48.702 0 49.2107 0.210714 49.5858 0.585786L63.4142 14.4142C63.7893 14.7893 64 15.298 64 15.8284V18H47C46.4477 18 46 17.5523 46 17Z" fill="#A0D6FF"/>
|
||||
<path d="M20.6094 49.321H17V39.1392H20.6392C21.6634 39.1392 22.545 39.343 23.2841 39.7507C24.0232 40.1551 24.5916 40.7367 24.9893 41.4957C25.3904 42.2547 25.5909 43.1629 25.5909 44.2202C25.5909 45.2808 25.3904 46.1922 24.9893 46.9545C24.5916 47.7169 24.0199 48.3018 23.2741 48.7095C22.5317 49.1172 21.6435 49.321 20.6094 49.321ZM19.1527 47.4766H20.5199C21.1562 47.4766 21.6915 47.3639 22.1257 47.1385C22.5632 46.9098 22.8913 46.5568 23.1101 46.0795C23.3321 45.599 23.4432 44.9792 23.4432 44.2202C23.4432 43.4678 23.3321 42.853 23.1101 42.3757C22.8913 41.8984 22.5649 41.5471 22.1307 41.3217C21.6965 41.0964 21.1612 40.9837 20.5249 40.9837H19.1527V47.4766Z" fill="#1099FF"/>
|
||||
<path d="M36.5421 44.2301C36.5421 45.3404 36.3316 46.285 35.9107 47.0639C35.4931 47.8428 34.923 48.4377 34.2005 48.8487C33.4812 49.2564 32.6725 49.4602 31.7743 49.4602C30.8695 49.4602 30.0575 49.2547 29.3382 48.8438C28.619 48.4328 28.0506 47.8378 27.633 47.0589C27.2154 46.2801 27.0066 45.3371 27.0066 44.2301C27.0066 43.1198 27.2154 42.1752 27.633 41.3963C28.0506 40.6174 28.619 40.0241 29.3382 39.6165C30.0575 39.2055 30.8695 39 31.7743 39C32.6725 39 33.4812 39.2055 34.2005 39.6165C34.923 40.0241 35.4931 40.6174 35.9107 41.3963C36.3316 42.1752 36.5421 43.1198 36.5421 44.2301ZM34.3596 44.2301C34.3596 43.5109 34.2518 42.9044 34.0364 42.4105C33.8243 41.9167 33.5243 41.5421 33.1365 41.2869C32.7488 41.0317 32.2947 40.9041 31.7743 40.9041C31.254 40.9041 30.7999 41.0317 30.4121 41.2869C30.0243 41.5421 29.7227 41.9167 29.5073 42.4105C29.2952 42.9044 29.1891 43.5109 29.1891 44.2301C29.1891 44.9493 29.2952 45.5559 29.5073 46.0497C29.7227 46.5436 30.0243 46.9181 30.4121 47.1733C30.7999 47.4285 31.254 47.5561 31.7743 47.5561C32.2947 47.5561 32.7488 47.4285 33.1365 47.1733C33.5243 46.9181 33.8243 46.5436 34.0364 46.0497C34.2518 45.5559 34.3596 44.9493 34.3596 44.2301Z" fill="#1099FF"/>
|
||||
<path d="M47.1204 42.7038H44.9428C44.9031 42.4221 44.8219 42.1719 44.6992 41.9531C44.5766 41.7311 44.4192 41.5421 44.2269 41.3864C44.0347 41.2306 43.8126 41.1113 43.5607 41.0284C43.3121 40.9455 43.042 40.9041 42.7504 40.9041C42.2234 40.9041 41.7643 41.035 41.3732 41.2969C40.9821 41.5554 40.6789 41.9332 40.4634 42.4304C40.248 42.9242 40.1403 43.5241 40.1403 44.2301C40.1403 44.956 40.248 45.5658 40.4634 46.0597C40.6822 46.5535 40.9871 46.9264 41.3782 47.1783C41.7693 47.4302 42.2217 47.5561 42.7354 47.5561C43.0238 47.5561 43.2906 47.518 43.5359 47.4418C43.7844 47.3655 44.0049 47.2545 44.1971 47.1087C44.3893 46.9595 44.5484 46.7789 44.6744 46.5668C44.8036 46.3546 44.8931 46.1127 44.9428 45.8409L47.1204 45.8509C47.064 46.3182 46.9232 46.7689 46.6978 47.2031C46.4757 47.634 46.1758 48.0201 45.7979 48.3615C45.4234 48.6996 44.976 48.968 44.4556 49.1669C43.9386 49.3625 43.3536 49.4602 42.7006 49.4602C41.7925 49.4602 40.9805 49.2547 40.2646 48.8438C39.552 48.4328 38.9885 47.8378 38.5742 47.0589C38.1632 46.2801 37.9577 45.3371 37.9577 44.2301C37.9577 43.1198 38.1665 42.1752 38.5842 41.3963C39.0018 40.6174 39.5685 40.0241 40.2844 39.6165C41.0004 39.2055 41.8058 39 42.7006 39C43.2906 39 43.8375 39.0829 44.3413 39.2486C44.8484 39.4143 45.2975 39.6562 45.6886 39.9744C46.0797 40.2893 46.3978 40.6754 46.6431 41.1328C46.8917 41.5902 47.0508 42.1139 47.1204 42.7038Z" fill="#1099FF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
8
src/components/Attachment/assets/file.svg
Normal file
8
src/components/Attachment/assets/file.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<svg width="64" height="88" viewBox="0 0 64 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 2C0.5 1.17157 1.17157 0.5 2 0.5H46.1855C46.5771 0.5 46.9532 0.653173 47.2334 0.926789L63.048 16.3692C63.337 16.6515 63.5 17.0384 63.5 17.4424V86C63.5 86.8284 62.8284 87.5 62 87.5H2C1.17157 87.5 0.5 86.8284 0.5 86V2Z" fill="#F0F3FA" stroke="#E3E9F6"/>
|
||||
<path d="M46 17V0H48.1716C48.702 0 49.2107 0.210714 49.5858 0.585786L63.4142 14.4142C63.7893 14.7893 64 15.298 64 15.8284V18H47C46.4477 18 46 17.5523 46 17Z" fill="#BDC9E0"/>
|
||||
<path d="M19 49.1818V39H25.7415V40.7749H21.1527V43.201H25.294V44.9759H21.1527V49.1818H19Z" fill="#616A7D"/>
|
||||
<path d="M29.3422 39V49.1818H27.1895V39H29.3422Z" fill="#616A7D"/>
|
||||
<path d="M31.1133 49.1818V39H33.266V47.407H37.631V49.1818H31.1133Z" fill="#616A7D"/>
|
||||
<path d="M39.0566 49.1818V39H45.9174V40.7749H41.2093V43.201H45.5645V44.9759H41.2093V47.407H45.9373V49.1818H39.0566Z" fill="#616A7D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 937 B |
7
src/components/Attachment/assets/pdf.svg
Normal file
7
src/components/Attachment/assets/pdf.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg width="64" height="88" viewBox="0 0 64 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 2C0.5 1.17157 1.17157 0.5 2 0.5H46.1855C46.5771 0.5 46.9532 0.653173 47.2334 0.926789L63.048 16.3692C63.337 16.6515 63.5 17.0384 63.5 17.4424V86C63.5 86.8284 62.8284 87.5 62 87.5H2C1.17157 87.5 0.5 86.8284 0.5 86V2Z" fill="#FFF6EE" stroke="#FEE5CD"/>
|
||||
<path d="M46 17V0H48.1716C48.702 0 49.2107 0.210714 49.5858 0.585786L63.4142 14.4142C63.7893 14.7893 64 15.298 64 15.8284V18H47C46.4477 18 46 17.5523 46 17Z" fill="#FECC9A"/>
|
||||
<path d="M19 49.1818V39H23.017C23.7893 39 24.4472 39.1475 24.9908 39.4425C25.5343 39.7341 25.9486 40.1402 26.2337 40.6605C26.522 41.1776 26.6662 41.7741 26.6662 42.4503C26.6662 43.1264 26.5204 43.723 26.2287 44.2401C25.937 44.7571 25.5144 45.1598 24.9609 45.4482C24.4107 45.7365 23.7446 45.8807 22.9624 45.8807H20.402V44.1555H22.6143C23.0286 44.1555 23.37 44.0843 23.6385 43.9418C23.9103 43.7959 24.1125 43.5954 24.245 43.3402C24.3809 43.0817 24.4489 42.785 24.4489 42.4503C24.4489 42.1122 24.3809 41.8172 24.245 41.5653C24.1125 41.3101 23.9103 41.1129 23.6385 40.9737C23.3667 40.8312 23.022 40.7599 22.6044 40.7599H21.1527V49.1818H19Z" fill="#FB7F03"/>
|
||||
<path d="M31.6738 49.1818H28.0645V39H31.7037C32.7278 39 33.6094 39.2038 34.3485 39.6115C35.0877 40.0159 35.6561 40.5975 36.0538 41.3565C36.4548 42.1155 36.6554 43.0237 36.6554 44.081C36.6554 45.1416 36.4548 46.053 36.0538 46.8153C35.6561 47.5777 35.0843 48.1626 34.3386 48.5703C33.5962 48.978 32.7079 49.1818 31.6738 49.1818ZM30.2172 47.3374H31.5843C32.2207 47.3374 32.756 47.2247 33.1902 46.9993C33.6277 46.7706 33.9558 46.4176 34.1745 45.9403C34.3966 45.4598 34.5076 44.84 34.5076 44.081C34.5076 43.3286 34.3966 42.7138 34.1745 42.2365C33.9558 41.7592 33.6293 41.4079 33.1951 41.1825C32.761 40.9571 32.2257 40.8445 31.5893 40.8445H30.2172V47.3374Z" fill="#FB7F03"/>
|
||||
<path d="M38.25 49.1818V39H44.9915V40.7749H40.4027V43.201H44.544V44.9759H40.4027V49.1818H38.25Z" fill="#FB7F03"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
7
src/components/Attachment/assets/xls.svg
Normal file
7
src/components/Attachment/assets/xls.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg width="64" height="88" viewBox="0 0 64 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 2C0.5 1.17157 1.17157 0.5 2 0.5H46.1855C46.5771 0.5 46.9532 0.653173 47.2334 0.926789L63.048 16.3692C63.337 16.6515 63.5 17.0384 63.5 17.4424V86C63.5 86.8284 62.8284 87.5 62 87.5H2C1.17157 87.5 0.5 86.8284 0.5 86V2Z" fill="#EEFDF7" stroke="#CDF8E6"/>
|
||||
<path d="M46 17V0H48.1716C48.702 0 49.2107 0.210714 49.5858 0.585786L63.4142 14.4142C63.7893 14.7893 64 15.298 64 15.8284V18H47C46.4477 18 46 17.5523 46 17Z" fill="#9BF0CC"/>
|
||||
<path d="M21.5007 39.1392L23.554 42.6094H23.6335L25.6967 39.1392H28.1278L25.0206 44.2301L28.1974 49.321H25.7216L23.6335 45.8459H23.554L21.4659 49.321H19L22.1868 44.2301L19.0597 39.1392H21.5007Z" fill="#1DC37D"/>
|
||||
<path d="M35.0135 42.0675C34.9737 41.6664 34.803 41.3549 34.5014 41.1328C34.1998 40.9107 33.7905 40.7997 33.2734 40.7997C32.9221 40.7997 32.6255 40.8494 32.3835 40.9489C32.1416 41.045 31.956 41.1792 31.8267 41.3516C31.7008 41.5239 31.6378 41.7195 31.6378 41.9382C31.6312 42.1205 31.6693 42.2796 31.7521 42.4155C31.8383 42.5514 31.956 42.669 32.1051 42.7685C32.2543 42.8646 32.4266 42.9491 32.6222 43.022C32.8177 43.0916 33.0265 43.1513 33.2486 43.201L34.1634 43.4197C34.6075 43.5192 35.0152 43.6518 35.3864 43.8175C35.7576 43.9832 36.0791 44.187 36.3509 44.429C36.6226 44.6709 36.8331 44.956 36.9822 45.2841C37.1347 45.6122 37.2126 45.9884 37.2159 46.4126C37.2126 47.0357 37.0535 47.576 36.7386 48.0334C36.4271 48.4875 35.9763 48.8404 35.3864 49.0923C34.7997 49.3409 34.0921 49.4652 33.2635 49.4652C32.4415 49.4652 31.7256 49.3393 31.1158 49.0874C30.5092 48.8355 30.0353 48.4626 29.6939 47.9688C29.3558 47.4716 29.1785 46.8568 29.1619 46.1243H31.245C31.2682 46.4657 31.366 46.7507 31.5384 46.9794C31.714 47.2048 31.9477 47.3755 32.2393 47.4915C32.5343 47.6042 32.8674 47.6605 33.2386 47.6605C33.6032 47.6605 33.9197 47.6075 34.1882 47.5014C34.46 47.3954 34.6705 47.2479 34.8196 47.0589C34.9688 46.87 35.0433 46.6529 35.0433 46.4077C35.0433 46.179 34.9754 45.9867 34.8395 45.831C34.7069 45.6752 34.5114 45.5426 34.2528 45.4332C33.9976 45.3239 33.6844 45.2244 33.3132 45.1349L32.2045 44.8565C31.3461 44.6477 30.6683 44.3213 30.1712 43.8771C29.674 43.433 29.4271 42.8348 29.4304 42.0824C29.4271 41.4659 29.5911 40.9273 29.9226 40.4666C30.2573 40.0059 30.7164 39.6463 31.2997 39.3878C31.883 39.1293 32.5459 39 33.2884 39C34.044 39 34.7036 39.1293 35.267 39.3878C35.8338 39.6463 36.2746 40.0059 36.5895 40.4666C36.9044 40.9273 37.0668 41.4609 37.0767 42.0675H35.0135Z" fill="#1DC37D"/>
|
||||
<path d="M38.6639 49.321V39.1392H40.8166V47.5462H45.1816V49.321H38.6639Z" fill="#1DC37D"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
src/components/Attachment/index.ts
Normal file
1
src/components/Attachment/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Attachment';
|
|
@ -1,11 +1,11 @@
|
|||
@import 'src/app/styles/vars';
|
||||
|
||||
$button-border-width: 2px;
|
||||
$button-box-shadow-sizes: 0 20px 80px -20px;
|
||||
|
||||
.Button {
|
||||
//@include text-body-m-medium;
|
||||
@include text-body-m-regular;
|
||||
@include transition(box-shadow, transform, border-color);
|
||||
@include transition(box-shadow, transform, border-color, background-color);
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -29,10 +29,16 @@ $button-border-width: 2px;
|
|||
outline: none;
|
||||
}
|
||||
|
||||
&.Button_size_large:hover,
|
||||
&.Button_size_large.Button_hovered,
|
||||
&.Button_size_large_x:hover,
|
||||
&.Button_size_large_x.Button_hovered, {
|
||||
//transform: translate(0, -3px);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.Button_hovered {
|
||||
transform: translate(0, -3px);
|
||||
|
||||
//transform: translate(0, -3px);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +67,10 @@ $button-border-width: 2px;
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.Button__loader {
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.Button__loader,
|
||||
.Button__contentLeft,
|
||||
.Button__contentRight {
|
||||
|
@ -77,6 +87,12 @@ $button-border-width: 2px;
|
|||
margin-left: $spacing-small-3x;
|
||||
}
|
||||
|
||||
.Button_size_small_x {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
font-size: $font-size-14;
|
||||
}
|
||||
|
||||
.Button_size_small {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
|
@ -93,29 +109,44 @@ $button-border-width: 2px;
|
|||
min-height: 48px;
|
||||
}
|
||||
|
||||
.Button_size_large_x {
|
||||
@include text-body-l-regular;
|
||||
height: 64px;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.Button_variant_primary {
|
||||
background-color: $color-brand-primary;
|
||||
background-color: $color-button-primary-default-fill;
|
||||
color: $color-text-quaternary;
|
||||
|
||||
&.Button_hovered,
|
||||
&:hover {
|
||||
//background-color: $color-brand-hover;
|
||||
box-shadow: 0 20px 80px -10px $color-brand-primary;
|
||||
background-color: $color-button-primary-hover-fill;
|
||||
//box-shadow: #{$button-box-shadow-sizes} $color-brand-primary;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $color-button-primary-pressed-fill;
|
||||
}
|
||||
|
||||
&.Button_disabled {
|
||||
color: $color-text-tertiary;
|
||||
//color: $color-text-tertiary;
|
||||
background-color: $color-brand-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.Button_variant_secondary {
|
||||
background-color: $color-background-secondary;
|
||||
color: $color-text-primary;
|
||||
background-color: $color-button-secondary-default-fill;
|
||||
color: $color-text-brand;
|
||||
|
||||
&.Button_hovered,
|
||||
&:hover {
|
||||
//background-color: $color-on-surface-dark-400;
|
||||
//box-shadow: #{$button-box-shadow-sizes} $color-background-dark-400;
|
||||
background-color: $color-button-secondary-hover-fill;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $color-button-secondary-pressed-fill;
|
||||
}
|
||||
|
||||
&.Button_disabled {
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import React, { ElementType, useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { PolyExtends } from '../../utils/types';
|
||||
import { Loader } from '../Loader';
|
||||
import s from './Button.module.scss';
|
||||
import {PolyExtends} from '../../utils/types';
|
||||
|
||||
export enum ButtonSize {
|
||||
small_x = 'small_x',
|
||||
small = 'small',
|
||||
medium = 'medium',
|
||||
large = 'large'
|
||||
large = 'large',
|
||||
large_x = 'large_x'
|
||||
}
|
||||
|
||||
export enum ButtonVariant {
|
||||
|
@ -138,7 +141,7 @@ export function Button<ComponentType extends ElementType = ButtonDefaultComponen
|
|||
<div className={clsx(s.Button__content, classes?.content)}>
|
||||
<div className={classes?.text}>{children}</div>
|
||||
|
||||
{/*{isLoading && <Loader className={s.Button__loader} size={LoaderSize.small} />}*/}
|
||||
{isLoading && <Loader className={s.Button__loader} />}
|
||||
</div>
|
||||
</Component>
|
||||
);
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
.Divider {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
background-color: $color-on-surface-dark-300;
|
||||
background-color: $color-divider-dark;
|
||||
}
|
||||
|
||||
.Divider_variant_horizontal {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
|
@ -15,10 +16,10 @@
|
|||
height: auto;
|
||||
}
|
||||
|
||||
//.Divider_color_light {
|
||||
// background-color: $color-on-surface-light-200;
|
||||
//}
|
||||
//
|
||||
//.Divider_color_dark {
|
||||
// background-color: $color-on-surface-dark-300;
|
||||
//}
|
||||
.Divider_color_light {
|
||||
background-color: $color-background-secondary;
|
||||
}
|
||||
|
||||
.Divider_color_dark {
|
||||
background-color: $color-divider-dark;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ export interface IDividerProps extends IntrinsicPropsWithoutRef<'hr'> {
|
|||
|
||||
export function Divider({
|
||||
className,
|
||||
variant = DividerVariant.vertical,
|
||||
variant = DividerVariant.horizontal,
|
||||
color = DividerColor.dark,
|
||||
innerRef,
|
||||
...props
|
||||
|
|
|
@ -2,29 +2,25 @@
|
|||
|
||||
.Input {
|
||||
@include text-body-m-regular;
|
||||
@include transition(border-color, box-shadow);
|
||||
@include transition(border-color);
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 50px;
|
||||
max-height: 300px;
|
||||
padding: 15px $spacing-small-x;
|
||||
color: $color-text-primary;
|
||||
overflow: hidden;
|
||||
border: 1px solid transparent;
|
||||
border: 1px solid $color-border-default;
|
||||
border-radius: $radius-small;
|
||||
background-color: $color-on-surface-dark-400;
|
||||
background-color: $color-background-primary;
|
||||
cursor: text;
|
||||
box-sizing: border-box;
|
||||
|
||||
overflow-y: auto;
|
||||
word-wrap: break-word;
|
||||
|
||||
@include scrollbar;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-hover;
|
||||
&:hover:not(.Input_focus, .Input_error) {
|
||||
border-color: $color-border-hover;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +29,7 @@
|
|||
}
|
||||
|
||||
.Input_focus {
|
||||
border-color: $color-brand-primary;
|
||||
border-color: $color-border-active;
|
||||
}
|
||||
|
||||
.Input_disabled {
|
||||
|
@ -56,8 +52,8 @@
|
|||
min-width: 0;
|
||||
height: 100%;
|
||||
|
||||
resize: none;
|
||||
white-space: break-spaces;
|
||||
//resize: none;
|
||||
//white-space: break-spaces;
|
||||
|
||||
&:placeholder-shown + .Input__clear {
|
||||
display: none;
|
||||
|
@ -86,7 +82,7 @@
|
|||
}
|
||||
|
||||
.Input__input::placeholder {
|
||||
color: $color-text-tertiary;
|
||||
color: $color-text-secondary;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,10 +65,10 @@ const TextareaForwardedRef = React.forwardRef<HTMLDivElement, TextAreaProps>(
|
|||
const delegateProps = useDelegateFocus<HTMLDivElement, HTMLInputElement>(inputRef, { onClick });
|
||||
const { focused, ...focusProps } = useFocus({ ...inputProps, ...registration });
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.style.height = '1px';
|
||||
inputRef.current.style.height = inputRef.current.scrollHeight + 'px';
|
||||
}
|
||||
// if (inputRef.current) {
|
||||
// inputRef.current.style.height = '1px';
|
||||
// inputRef.current.style.height = inputRef.current.scrollHeight + 'px';
|
||||
// }
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -94,16 +94,6 @@ const TextareaForwardedRef = React.forwardRef<HTMLDivElement, TextAreaProps>(
|
|||
ref={composeRefs(inputRef, inputRefProp, registration?.ref)}
|
||||
{...inputProps}
|
||||
{...focusProps}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
onInput={(e) => {
|
||||
setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.style.height = '1px';
|
||||
inputRef.current.style.height = inputRef.current.scrollHeight + 'px';
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
15
src/components/Upload/Upload.module.scss
Normal file
15
src/components/Upload/Upload.module.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@import 'src/app/styles/vars';
|
||||
|
||||
.Upload {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.Upload__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Upload__input {
|
||||
display: none;
|
||||
}
|
28
src/components/Upload/Upload.tsx
Normal file
28
src/components/Upload/Upload.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { forwardRef, Ref, useId } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { ReactFCC } from '../../utils/ReactFCC';
|
||||
import { IntrinsicPropsWithoutRef } from '../../utils/types';
|
||||
import s from './Upload.module.scss';
|
||||
|
||||
export interface UploadButtonProps extends IntrinsicPropsWithoutRef<'input'> {
|
||||
/**
|
||||
* Дополнительный css-класс
|
||||
*/
|
||||
className?: string;
|
||||
// onChange?: (e: any) => void;
|
||||
// multiple?: boolean;
|
||||
}
|
||||
|
||||
export const Upload = forwardRef(function Upload(props: UploadButtonProps, ref: Ref<HTMLInputElement>) {
|
||||
const { children, className, ...inputProps } = props;
|
||||
|
||||
const id = useId();
|
||||
|
||||
return (
|
||||
<label htmlFor={id} className={clsx(s.Upload, className)}>
|
||||
{/*<UploadIcon className={s.UploadButton__icon} />*/}
|
||||
{children}
|
||||
<input className={s.Upload__input} type="file" id={id} ref={ref} {...inputProps} />
|
||||
</label>
|
||||
);
|
||||
});
|
1
src/components/Upload/index.ts
Normal file
1
src/components/Upload/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Upload';
|
|
@ -1,17 +0,0 @@
|
|||
@import 'src/app/styles/vars';
|
||||
|
||||
.UploadButton {
|
||||
width: 100%;
|
||||
height: 80px !important;
|
||||
//background-color: $color-on-surface-dark-400;
|
||||
}
|
||||
|
||||
.UploadButton__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.UploadButton__input {
|
||||
display: none;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import clsx from 'clsx';
|
||||
import s from './UploadButton.module.scss';
|
||||
import {ReactFCC} from '../../utils/ReactFCC';
|
||||
import {Button, ButtonVariant} from '../Button';
|
||||
import { ReactComponent as UploadIcon } from '../../assets/icons/upload.svg';
|
||||
import {useId} from 'react';
|
||||
|
||||
export interface UploadButtonProps {
|
||||
/**
|
||||
* Дополнительный css-класс
|
||||
*/
|
||||
className?: string;
|
||||
onChange?: (e: any) => void;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
export const UploadButton: ReactFCC<UploadButtonProps> = (props) => {
|
||||
const {className, onChange, multiple} = props;
|
||||
|
||||
const id = useId();
|
||||
|
||||
return (
|
||||
<Button component={'label'} htmlFor={id} className={clsx(s.UploadButton, className)} variant={ButtonVariant.secondary}>
|
||||
<UploadIcon className={s.UploadButton__icon} />
|
||||
<input
|
||||
className={s.UploadButton__input}
|
||||
type="file"
|
||||
id={id}
|
||||
onChange={onChange}
|
||||
multiple={multiple}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './UploadButton';
|
|
@ -1,6 +1,7 @@
|
|||
// 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 = 'https://16c2-77-234-219-9.ngrok-free.app';
|
||||
export const BACKEND_URL = 'http://192.168.107.4';
|
||||
|
||||
export const API_URL = BACKEND_URL + '/api';
|
||||
export const API_URL = BACKEND_URL + '/api';
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
||||
import {ChangeEvent} from 'react';
|
||||
import s from '../ChatFormPhotoDescription/ChatFormPhotoDescription.module.scss';
|
||||
import {UploadButton} from '../../../../../../components/UploadButton';
|
||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
||||
import {SimpleButton} from '../../../SimpleButton';
|
||||
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;
|
||||
|
@ -14,36 +14,37 @@ export interface ChatFormMultiplePhotoProps {
|
|||
}
|
||||
|
||||
export const ChatFormMultiplePhoto: ReactFCC<ChatFormMultiplePhotoProps> = (props) => {
|
||||
const {className, onSubmit, registration, control } = 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>
|
||||
))}
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
{value && Object.values(value).map((file: any, index) => <p key={index}>Загружен файл: {file.name}</p>)}
|
||||
|
||||
<UploadButton
|
||||
multiple={true}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files) {
|
||||
return;
|
||||
}
|
||||
<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;
|
||||
});
|
||||
const files: any = { ...value };
|
||||
Array.from(e.target.files).forEach((file, index) => {
|
||||
files[`file_${Object.keys(files).length + 1}`] = file;
|
||||
});
|
||||
|
||||
onChange({ ...files })
|
||||
}}
|
||||
/>
|
||||
onChange({ ...files });
|
||||
}}
|
||||
/>
|
||||
|
||||
<SimpleButton onClick={onSubmit} />
|
||||
</>
|
||||
)} name={registration.name!} />
|
||||
<SimpleButton onClick={onSubmit} />
|
||||
</>
|
||||
)}
|
||||
name={registration.name!}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ChangeEvent } from 'react';
|
||||
import { Control, Controller, FieldValues, UseFormRegisterReturn } from 'react-hook-form';
|
||||
import clsx from 'clsx';
|
||||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
||||
import {SimpleButton} from '../../../SimpleButton';
|
||||
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 {ChangeEvent} from 'react';
|
||||
import {UploadButton} from '../../../../../../components/UploadButton';
|
||||
import { Textarea } from '../../../../../../components/Textarea';
|
||||
import { Button, ButtonVariant } from '../../../../../../components/Button';
|
||||
import { Upload } from '../../../../../../components/Upload';
|
||||
|
||||
export interface ChatFormMultiplePhotoDescriptionProps {
|
||||
className?: string;
|
||||
|
@ -16,58 +16,67 @@ export interface ChatFormMultiplePhotoDescriptionProps {
|
|||
}
|
||||
|
||||
export const ChatFormMultiplePhotoDescription: ReactFCC<ChatFormMultiplePhotoDescriptionProps> = (props) => {
|
||||
const {className, registration, control, onSubmit} = 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>
|
||||
)}
|
||||
<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>
|
||||
<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>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<UploadButton onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
<Upload
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange([...value, { file, text: '' }]);
|
||||
e.target.value = '';
|
||||
}} />
|
||||
onChange([...value, { file, text: '' }]);
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
|
||||
<SimpleButton className={s.ChatFormMultipleRange__button} onClick={onSubmit} />
|
||||
</>
|
||||
)} name={registration.name!} />
|
||||
<SimpleButton className={s.ChatFormMultipleRange__button} onClick={onSubmit} />
|
||||
</>
|
||||
)}
|
||||
name={registration.name!}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {ReactFCC} from '../../../../../../utils/ReactFCC';
|
||||
import {Textarea} from '../../../../../../components/Textarea';
|
||||
import {ChangeEvent} from 'react';
|
||||
import s from './ChatFormPhotoDescription.module.scss';
|
||||
import {UploadButton} from '../../../../../../components/UploadButton';
|
||||
import {Control, Controller, FieldValues, UseFormRegisterReturn} from 'react-hook-form';
|
||||
import {SimpleButton} from '../../../SimpleButton';
|
||||
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;
|
||||
|
@ -15,38 +15,41 @@ export interface ChatFormPhotoDescriptionProps {
|
|||
}
|
||||
|
||||
export const ChatFormPhotoDescription: ReactFCC<ChatFormPhotoDescriptionProps> = (props) => {
|
||||
const {className, onSubmit, registration, control } = 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>
|
||||
)}
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
{value.file && <p>Загружен файл: {value.file.name}</p>}
|
||||
|
||||
<UploadButton onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
<Upload
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange({ ...value, file });
|
||||
e.target.value = '';
|
||||
}} />
|
||||
onChange({ ...value, file });
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
placeholder={'Текст'}
|
||||
value={value.text}
|
||||
onChange={(e) => {
|
||||
onChange({ ...value, text: e.target.value })
|
||||
}}
|
||||
/>
|
||||
<Textarea
|
||||
placeholder={'Текст'}
|
||||
value={value.text}
|
||||
onChange={(e) => {
|
||||
onChange({ ...value, text: e.target.value });
|
||||
}}
|
||||
/>
|
||||
|
||||
<SimpleButton onClick={onSubmit} />
|
||||
</>
|
||||
)} name={registration.name!} />
|
||||
<SimpleButton onClick={onSubmit} />
|
||||
</>
|
||||
)}
|
||||
name={registration.name!}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
@import 'src/app/styles/vars';
|
||||
|
||||
$textarea-height: 192px;
|
||||
|
||||
.HomePage {
|
||||
//padding: $spacing-small-x;
|
||||
padding-bottom: $spacing-medium-x;
|
||||
}
|
||||
|
||||
.HomePage__title {
|
||||
//@include text-programming-code-regular;
|
||||
font-family: $font-family-ibm-plex-mono, $font-family-mono;
|
||||
margin-bottom: $spacing-small-x;
|
||||
}
|
||||
|
@ -17,17 +18,89 @@
|
|||
}
|
||||
|
||||
.HomePage__box {
|
||||
@include flex-col-middle;
|
||||
background-color: $color-background-dark-300;
|
||||
@include flex-col;
|
||||
background-color: $color-background-dark-100;
|
||||
border-radius: $radius-large;
|
||||
padding: $spacing-medium;
|
||||
}
|
||||
|
||||
.HomePage__dropBox {
|
||||
@include flex-col;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.HomePage__dropBoxPlaceholder {
|
||||
position: absolute;
|
||||
top: -$spacing-medium;
|
||||
left: -$spacing-medium;
|
||||
width: calc(100% + $spacing-medium * 2);
|
||||
height: calc(100% + $spacing-medium * 2);
|
||||
visibility: hidden;
|
||||
background-color: rgba($color-background-accent, 0.6);
|
||||
border-radius: $radius-small;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.HomePage__dropBoxPlaceholderIcon {
|
||||
width: 144px;
|
||||
height: 144px;
|
||||
color: $color-background-primary;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.HomePage__dropBox_hidden {
|
||||
.HomePage__dropBoxPlaceholder {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.HomePage__button {
|
||||
margin-bottom: $spacing-small-2x;
|
||||
height: 64px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.HomePage__buttonHint {
|
||||
color: $color-text-tertiary;
|
||||
}
|
||||
|
||||
.HomePage__divider {
|
||||
margin: $spacing-small 0;
|
||||
}
|
||||
|
||||
.HomePage__textarea, .HomePage__filesContainer {
|
||||
width: 100%;
|
||||
margin-bottom: $spacing-small-3x;
|
||||
min-height: $textarea-height;
|
||||
}
|
||||
|
||||
.HomePage__filesContainer {
|
||||
@include flex-middle;
|
||||
flex-wrap: wrap;
|
||||
gap: $spacing-small-x;
|
||||
border: 1px solid $color-border-default;
|
||||
border-radius: $radius-small;
|
||||
background-color: $color-background-primary;
|
||||
padding: $spacing-small-x;
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: $color-text-tertiary;
|
||||
margin-bottom: $spacing-small-3x;
|
||||
}
|
||||
|
||||
.HomePage__orHint {
|
||||
margin-bottom: $spacing-small-3x;
|
||||
letter-spacing: 0.06rem;
|
||||
//color: $color-text-tertiary;
|
||||
//margin-left: 70px;
|
||||
}
|
|
@ -1,10 +1,117 @@
|
|||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import clsx from 'clsx';
|
||||
import { ReactFCC } from '../../utils/ReactFCC';
|
||||
import { Heading, HeadingSize } from '../../components/Heading';
|
||||
import { ETextVariants, Text } from '../../components/Text';
|
||||
import { Button, ButtonSize } from '../../components/Button';
|
||||
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 { 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: process, refetch: refetchProcess } = useProcess({
|
||||
processId: processId || '',
|
||||
config: {
|
||||
enabled: !!processId
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync: createProcess, isLoading } = useCreateProcess();
|
||||
|
||||
const timeout = useSingleTimeout();
|
||||
|
||||
const onSubmit: SubmitHandler<FormFields> = useCallback(
|
||||
async (data) => {
|
||||
const response = await createProcess({
|
||||
text: data.text
|
||||
});
|
||||
|
||||
setProcessId(response.id);
|
||||
},
|
||||
[createProcess]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (processId) {
|
||||
const startPolling = () => {
|
||||
timeout.set(async () => {
|
||||
const { data: process } = await refetchProcess();
|
||||
if (process && process.done < process.count) {
|
||||
startPolling();
|
||||
}
|
||||
}, PROCESS_POLLING_MS);
|
||||
};
|
||||
|
||||
if (processId) {
|
||||
startPolling();
|
||||
}
|
||||
}
|
||||
}, [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');
|
||||
const currentFiles = watch('files');
|
||||
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: File[]) => {
|
||||
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 isDisabled = !currentText && currentFiles.length === 0;
|
||||
|
||||
return (
|
||||
<div className={s.HomePage}>
|
||||
<Heading size={HeadingSize.H2} className={s.HomePage__title}>
|
||||
|
@ -12,27 +119,79 @@ export const HomePage: ReactFCC = () => {
|
|||
</Heading>
|
||||
|
||||
<Text className={s.HomePage__text} variant={ETextVariants.BODY_M_REGULAR}>
|
||||
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry
|
||||
standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make
|
||||
a type specimen book.
|
||||
Позволяет оценить кредитный рейтинг компании на основе пресс-релиза с выделением в тексте меток по различным
|
||||
метрикам.
|
||||
</Text>
|
||||
|
||||
<div className={s.HomePage__box}>
|
||||
<Button className={s.HomePage__button} size={ButtonSize.large}>
|
||||
Выбрать DOCX/PDF файл
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
{/*<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>
|
||||
|
||||
<Text className={s.HomePage__buttonHint} variant={ETextVariants.BODY_S_REGULAR}>
|
||||
Или перетащите файл мышкой
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/*<Button className={s.HomePage__button} size={ButtonSize.large}>*/}
|
||||
{/* Button*/}
|
||||
{/*</Button>*/}
|
||||
{/*<Button variant={ButtonVariant.secondary} size={ButtonSize.large}>*/}
|
||||
{/* Button*/}
|
||||
{/*</Button>*/}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
3
src/pages/home/assets/plus.svg
Normal file
3
src/pages/home/assets/plus.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 12C21 11.4477 20.5523 11 20 11H13V4C13 3.44772 12.5523 3 12 3C11.4477 3 11 3.44772 11 4V11H4C3.44772 11 3 11.4477 3 12C3 12.5523 3.44772 13 4 13H11V20C11 20.5523 11.4477 21 12 21C12.5523 21 13 20.5523 13 20V13H20C20.5523 13 21 12.5523 21 12Z" fill="currentCOlor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 381 B |
21
yarn.lock
21
yarn.lock
|
@ -3124,6 +3124,11 @@ at-least-node@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||
|
||||
attr-accept@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
|
||||
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
|
||||
|
||||
autoprefixer@^10.4.13:
|
||||
version "10.4.15"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.15.tgz#a1230f4aeb3636b89120b34a1f513e2f6834d530"
|
||||
|
@ -5059,6 +5064,13 @@ file-loader@^6.2.0:
|
|||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
file-selector@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc"
|
||||
integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
filelist@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
|
||||
|
@ -8331,6 +8343,15 @@ react-dom@^18.2.0:
|
|||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-dropzone@^14.2.3:
|
||||
version "14.2.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.3.tgz#0acab68308fda2d54d1273a1e626264e13d4e84b"
|
||||
integrity sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==
|
||||
dependencies:
|
||||
attr-accept "^2.2.2"
|
||||
file-selector "^0.6.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-error-overlay@^6.0.11:
|
||||
version "6.0.11"
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
|
||||
|
|
Loading…
Reference in New Issue
Block a user