code7-leia 1.0.10 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/index.es.js +3357 -2922
  2. package/package.json +6 -6
  3. package/src/components/CustomToast/index.tsx +62 -0
  4. package/src/components/CustomToast/styles.tsx +74 -0
  5. package/src/components/EmptyState/index.tsx +88 -0
  6. package/src/components/EmptyState/styles.tsx +81 -0
  7. package/src/components/FileArea/components/AreaUpload/index.tsx +137 -0
  8. package/src/components/FileArea/components/AreaUpload/styles.tsx +86 -0
  9. package/src/components/FileArea/components/Modal/ModalButtonClose.tsx +23 -0
  10. package/src/components/FileArea/components/Modal/ModalContent.tsx +26 -0
  11. package/src/components/FileArea/components/Modal/ModalFooter.tsx +18 -0
  12. package/src/components/FileArea/components/Modal/ModalHeader.tsx +18 -0
  13. package/src/components/FileArea/components/Modal/ModalTitle.tsx +18 -0
  14. package/src/components/FileArea/components/Modal/index.tsx +131 -0
  15. package/src/components/FileArea/components/Modal/styles.tsx +121 -0
  16. package/src/components/FileArea/components/Search/index.tsx +45 -0
  17. package/src/components/FileArea/components/Search/styles.tsx +26 -0
  18. package/src/components/FileArea/components/Spinner/index.tsx +22 -0
  19. package/src/components/FileArea/components/Spinner/styles.tsx +59 -0
  20. package/src/components/FileArea/components/Table/index.tsx +34 -0
  21. package/src/components/FileArea/components/Table/styles.tsx +60 -0
  22. package/src/components/FileArea/index.tsx +279 -0
  23. package/src/components/FileArea/styles.tsx +183 -0
  24. package/src/components/LengthCounter/index.tsx +29 -0
  25. package/src/components/LengthCounter/styles.ts +11 -0
  26. package/src/components/Modal/index.tsx +30 -0
  27. package/src/components/Modal/styles.ts +52 -0
  28. package/src/components/MultiSelect/index.tsx +102 -0
  29. package/src/components/MultiSelect/styles.tsx +85 -0
  30. package/src/components/PersonasArea/index.tsx +196 -0
  31. package/src/components/PersonasArea/styles.ts +137 -0
  32. package/src/components/Select/index.tsx +60 -0
  33. package/src/components/Select/styles.tsx +49 -0
  34. package/src/components/TestArea/components/InputTest/index.tsx +23 -0
  35. package/src/components/TestArea/components/InputTest/styles.tsx +28 -0
  36. package/src/components/TestArea/components/TextArea/index.tsx +97 -0
  37. package/src/components/TestArea/components/TextArea/styles.tsx +173 -0
  38. package/src/components/TestArea/index.tsx +99 -0
  39. package/src/components/TestArea/styles.tsx +112 -0
  40. package/src/contexts/LeiaProvider.tsx +133 -0
  41. package/src/index.tsx +6 -0
  42. package/src/interface/FileData.ts +11 -0
  43. package/src/interface/Language.ts +95 -0
  44. package/src/interface/Table.ts +12 -0
  45. package/src/service/Api.ts +9 -0
  46. package/src/service/ApiDev.ts +9 -0
  47. package/src/service/ApiHml.ts +9 -0
  48. package/src/store/index.ts +13 -0
  49. package/src/store/modules/actions.ts +88 -0
  50. package/src/store/modules/reducer.ts +46 -0
  51. package/src/store/modules/sagas.ts +127 -0
  52. package/src/store/modules/types.ts +19 -0
  53. package/src/types/image.d.ts +4 -0
  54. package/src/utils/formatAxios.tsx +15 -0
  55. package/src/utils/getApi.tsx +16 -0
  56. package/src/utils/getLanguage.tsx +17 -0
  57. package/src/utils/languages/en.ts +107 -0
  58. package/src/utils/languages/es.ts +107 -0
  59. package/src/utils/languages/pt-br.ts +111 -0
  60. package/src/utils/stringNormalize.ts +9 -0
  61. package/dist/code7-leia.css +0 -1
  62. package/dist/index.cjs.js +0 -1072
@@ -0,0 +1,279 @@
1
+ import React, { useEffect, useState} from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import { renderToString } from 'react-dom/server';
4
+
5
+ import { FaUpload, FaList, FaCheck, FaFile } from 'react-icons/fa';
6
+ import { GiWeightLiftingUp } from "react-icons/gi";
7
+ import { TiDelete } from "react-icons/ti";
8
+
9
+ import type { FileData, tag } from '../../interface/FileData'
10
+
11
+ import { getLanguage } from '../../utils/getLanguage'
12
+
13
+ import * as S from './styles';
14
+ import Table from './components/Table';
15
+ import Search from './components/Search';
16
+ import Modal from './components/Modal'
17
+ import ModalFooter from './components/Modal/ModalFooter'
18
+ import ModalContent from './components/Modal/ModalContent'
19
+ import EmptyState from '../EmptyState';
20
+ import MultiSelect from '../MultiSelect';
21
+ import AreaUpload from './components/AreaUpload';
22
+ import { useLeia } from '../../contexts/LeiaProvider';
23
+ import { deleteFilesAction, getFilesAction, uploadFilesAction, trainingAction } from '../../store/modules/actions';
24
+
25
+ export const FileArea = () => {
26
+ const { id, language, propTags, token, env, readonly } = useLeia();
27
+ const [files, setFiles] = useState<FileData[]>([]);
28
+ const [initialFiles, setInitialFiles] = useState<FileData[]>([]);
29
+ const [uploadFile, setUploadFile] = useState<{ content: ArrayBuffer | string; properties: File }>();
30
+ const [modal, setModal] = useState(false)
31
+ const [modalDelete, setModalDelete] = useState(false)
32
+ const [modalTraining, setModalTraining] = useState(false)
33
+ const [fileDelete, setFileDelete] = useState('')
34
+ const [optionsPresset, setOptionsPresset] = useState<{ label: any; value: any; }[]>([])
35
+ const [presset, setPresset] = useState<string[]>([]);
36
+ const t = getLanguage(language)
37
+ const dispatch = useDispatch();
38
+ const initFiles = useSelector((state: any) => state.files);
39
+ const isloading = useSelector((state: any) => state.isloading);
40
+ const tags = propTags ? propTags.tags : [];
41
+
42
+ useEffect(() => {
43
+ dispatch(getFilesAction(id, token, env))
44
+ }, [id])
45
+
46
+ useEffect(() => {
47
+ if(tags.length > 0){
48
+ const options = tags.map((tag: any) => {
49
+ return { label: tag, value: tag }
50
+ })
51
+ setOptionsPresset(options)
52
+ }else if(optionsPresset.length > 0){
53
+ setOptionsPresset([])
54
+ }
55
+
56
+ }, [tags])
57
+
58
+ useEffect(() => {
59
+ if(!modal){
60
+ const options = tags.map((tag: any) => {
61
+ return { label: tag, value: tag }
62
+ })
63
+ setOptionsPresset(options)
64
+ }
65
+ }, [modal])
66
+
67
+ useEffect(() => {
68
+ if(!!initFiles){
69
+ setFiles(initFiles)
70
+ setInitialFiles(initFiles)
71
+ }
72
+ }, [initFiles])
73
+
74
+ const pressetTag = (tags: [tag]) => {
75
+ let html = '';
76
+
77
+ html += tags.map(tag => {
78
+ const checkIcon = tag.trained ? '' : renderToString(<TiDelete size={14} />);
79
+ const tagClass = tag.trained ? 'trained' : '';
80
+ return `<p class='tag ${tagClass}'>${tag.name} ${checkIcon}</p>`;
81
+ }).join('');
82
+
83
+ return html;
84
+ }
85
+
86
+ const handleOpenModal = () => {
87
+ if(modal){
88
+ setUploadFile(undefined)
89
+ setPresset([])
90
+ }
91
+ setModal(!modal)
92
+ }
93
+
94
+ const handleOpenModalDelete = (name: string) => {
95
+ setFileDelete(name)
96
+ setModalDelete(!modalDelete)
97
+ }
98
+
99
+ const handleOpenModalTraining = (tags?: [tag]) => {
100
+ let pressets: string[] = []
101
+ tags?.map((tag) => {
102
+ if(!tag.trained){
103
+ pressets.push(tag.name)
104
+ }
105
+ })
106
+ setPresset(pressets)
107
+ setModalTraining(!modalTraining)
108
+ }
109
+
110
+ const deleteFile = () => {
111
+ dispatch(deleteFilesAction(fileDelete, id, language, token, env))
112
+ setModalDelete(false)
113
+ }
114
+
115
+ const handleUploadFile = () => {
116
+ dispatch(uploadFilesAction(uploadFile, id, presset, language, token, env))
117
+ setModal(false)
118
+ setUploadFile(undefined)
119
+ setPresset([])
120
+ };
121
+
122
+ const handleTrain = () => {
123
+ dispatch(trainingAction(id, presset, language, token, env))
124
+ setModalTraining(false)
125
+ setPresset([])
126
+ };
127
+
128
+ const viewFile = (link: string, name: string) => {
129
+ if(link){
130
+ const checkIcon = renderToString(<FaFile size={14} />);
131
+ return `<a href='${link}' target='_blank'>${checkIcon}</a> ${name}`;
132
+ }
133
+ return name
134
+ };
135
+
136
+ const renderFiles = () => {
137
+ return (
138
+ <S.Container isloading={isloading}>
139
+ <div id='loading' />
140
+ <S.Header>
141
+ <div className='infos'>
142
+ <h2>{t.files}</h2>
143
+ <p>{t.fileArea.description}</p>
144
+ </div>
145
+ <div className='actions'>
146
+ <Search placeholder={t.fileArea.search} setFiles={setFiles} initialFiles={initialFiles}></Search>
147
+ <div>
148
+ <button id='button-upload' disabled={readonly} onClick={handleOpenModal}><FaUpload size={14} /> {t.fileArea.fileUpload}</button>
149
+ <button disabled={readonly} onClick={() => handleOpenModalTraining(undefined)} ><GiWeightLiftingUp size={14} /> {t.fileArea.training}</button>
150
+ </div>
151
+ </div>
152
+ </S.Header>
153
+ {
154
+ files.length === 0 ? (
155
+ <EmptyState
156
+ icon={<FaList></FaList>}
157
+ title={t.fileArea.emptyState.title}
158
+ description={t.fileArea.emptyState.description}
159
+ iconButton={<button id='button-upload' disabled={readonly} onClick={handleOpenModal}><FaUpload size={14} /> {t.fileArea.fileUpload}</button>}
160
+ descriptionButton={t.fileArea.fileUpload}
161
+ widthButton="230px"
162
+ />
163
+ ) : <Table>
164
+ <thead>
165
+ <tr>
166
+ <th key="th_file_name" className="th_file_name">
167
+ {t.fileArea.fileName}
168
+ </th>
169
+ <th key="th_file_name" className="th_status">
170
+ {t.fileArea.status}
171
+ </th>
172
+ <th key="th_pressets" className="th_pressets">
173
+ {t.fileArea.presset}
174
+ </th>
175
+ <th key="th_actions" className="th_actions">
176
+ {t.fileArea.actions}
177
+ </th>
178
+ </tr>
179
+ </thead>
180
+
181
+ <tbody>
182
+ {files.map((object) => (
183
+ <tr>
184
+ <td dangerouslySetInnerHTML={{ __html: viewFile(object.link, object.name) }} />
185
+ <td>{object.trained ? <FaCheck size={14} style={{ color: 'green' }} /> : <TiDelete size={14} style={{ color: 'red' }} />}</td>
186
+ <td className='tags' dangerouslySetInnerHTML={{ __html: pressetTag(object.tags) }} />
187
+ <td>
188
+ <div className='divDelete'>
189
+ <button className='buttonTraining' disabled={readonly} onClick={() => handleOpenModalTraining(object.tags)}>{t.buttons.training}</button>
190
+ <button className='buttonDelete' disabled={readonly} onClick={() => handleOpenModalDelete(object.name)}>{t.buttons.delete}</button>
191
+ </div>
192
+ </td>
193
+ </tr>
194
+ ))}
195
+ </tbody>
196
+ </Table>
197
+ }
198
+ <Modal
199
+ isopen={modal}
200
+ maxwidth="600px"
201
+ maxheight="max-content"
202
+ onClose={handleOpenModal}
203
+ title={t.fileArea.modal.uploadFile}
204
+ >
205
+ <ModalContent>
206
+ <div className='choose-file'>
207
+ <AreaUpload
208
+ setFile={setUploadFile}
209
+ file={uploadFile?.properties.name}
210
+ />
211
+ </div>
212
+ <div className='presset'>
213
+ <p>{t.fileArea.presset}</p>
214
+ <MultiSelect setOptions={setOptionsPresset} options={optionsPresset} presset={presset} setPresset={setPresset} modal={modal} language={language} hasAddNewOptions={true} />
215
+ </div>
216
+ </ModalContent>
217
+ <ModalFooter>
218
+ <button className='button cancel' onClick={handleOpenModal}>
219
+ {t.buttons.cancel}
220
+ </button>
221
+ <button onClick={() => handleUploadFile()} className='button send' type="submit" form="form-tts">
222
+ {t.buttons.send}
223
+ </button>
224
+ </ModalFooter>
225
+
226
+ </Modal>
227
+ <Modal
228
+ isopen={modalDelete}
229
+ maxwidth="600px"
230
+ maxheight="max-content"
231
+ onClose={() => handleOpenModalDelete('')}
232
+ title={t.fileArea.modal.deleteFileTitle}
233
+ >
234
+ <ModalContent>
235
+ <p id='warning'>{t.fileArea.modal.deleteFile}</p>
236
+ </ModalContent>
237
+ <ModalFooter>
238
+ <button className='button cancel' onClick={() => handleOpenModalDelete('')}>
239
+ {t.buttons.cancel}
240
+ </button>
241
+ <button className='button delete' type="submit" form="form-tts" onClick={deleteFile}>
242
+ {t.buttons.delete}
243
+ </button>
244
+ </ModalFooter>
245
+ </Modal>
246
+ <Modal
247
+ isopen={modalTraining}
248
+ maxwidth="600px"
249
+ maxheight="max-content"
250
+ onClose={() => handleOpenModalTraining(undefined)}
251
+ title={t.fileArea.modal.trainingTitle}
252
+ >
253
+ <ModalContent>
254
+ <p id='info'>{t.fileArea.modal.trainingDescription}</p>
255
+ <div className='presset'>
256
+ <p>{t.fileArea.presset}</p>
257
+ <MultiSelect setOptions={setOptionsPresset} options={optionsPresset} presset={presset} setPresset={setPresset} modal={modal} language={language} hasAddNewOptions={false} />
258
+ </div>
259
+ </ModalContent>
260
+ <ModalFooter>
261
+ <button className='button cancel' onClick={() => handleOpenModalTraining(undefined)}>
262
+ {t.buttons.cancel}
263
+ </button>
264
+ <button onClick={() => handleTrain()} className='button send' type="submit" form="form-tts">
265
+ {t.buttons.send}
266
+ </button>
267
+ </ModalFooter>
268
+
269
+ </Modal>
270
+ </S.Container>
271
+ );
272
+ }
273
+
274
+ return (
275
+ <S.Container>
276
+ {renderFiles()}
277
+ </S.Container>
278
+ );
279
+ };
@@ -0,0 +1,183 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ interface ContainerProps {
4
+ isloading?: boolean;
5
+ }
6
+
7
+ export const Container = styled.div<ContainerProps>`
8
+ ${(props) =>
9
+ props.isloading &&
10
+ css`
11
+ #loading {
12
+ position: fixed;
13
+ top: 0;
14
+ left: 0;
15
+ width: 100%;
16
+ height: 100%;
17
+ background-color: rgb(151,154,165, 0.7);
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ z-index: 9999;
22
+ }
23
+
24
+ #loading::after {
25
+ content: "";
26
+ border: 4px solid #f3f3f3; /* Cor do símbolo de carregamento */
27
+ border-top: 4px solid #5a5d68; /* Cor do símbolo de carregamento */
28
+ border-radius: 50%;
29
+ width: 13px;
30
+ height: 13px;
31
+ animation: spin 1s linear infinite;
32
+ }
33
+
34
+ @keyframes spin {
35
+ 0% { transform: rotate(0deg); }
36
+ 100% { transform: rotate(360deg); }
37
+ }
38
+ `}
39
+
40
+
41
+ .tag {
42
+ padding: 4px 8px 4px 8px;
43
+ background: #C7F9ED;
44
+ border-radius: 4px;
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ }
49
+
50
+ .tags {
51
+ display: flex;
52
+ justify-content: left;
53
+ gap: 4px;
54
+ }
55
+
56
+ .divDelete {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: flex-start;
60
+ gap: 4px;
61
+ }
62
+
63
+ .buttonDelete {
64
+ color: #5b0a1f;
65
+ background: #fae0d2;
66
+ padding: 12px 20px;
67
+ border-radius: 4px;
68
+
69
+ &:disabled {
70
+ cursor: not-allowed;
71
+ opacity: 0.7;
72
+ }
73
+ }
74
+
75
+ .buttonDelete:hover {
76
+ background: #f5bba7;
77
+ }
78
+
79
+ .buttonTraining {
80
+ color: #023906;
81
+ background: #e3f8cc;
82
+ padding: 12px 20px;
83
+ border-radius: 4px;
84
+ margin-right: 10px;
85
+
86
+ &:disabled {
87
+ cursor: not-allowed;
88
+ opacity: 0.7;
89
+ }
90
+ }
91
+
92
+ .buttonTraining:hover {
93
+ background: #90d665;
94
+ }
95
+
96
+ .button {
97
+ padding: 8px 16px 8px 16px;
98
+ border-radius: 4px;
99
+ gap: 10px;
100
+ }
101
+
102
+ .cancel {
103
+ background: #F3F5F9;
104
+ color: #5A5D68;
105
+ }
106
+
107
+ .send {
108
+ background: #102693;
109
+ color: white;
110
+ }
111
+
112
+ .delete {
113
+ background: #871821;
114
+ color: white;
115
+ }
116
+
117
+ .presset {
118
+ padding-top: 10px;
119
+ display: flex;
120
+ flex-direction: column;
121
+
122
+ p {
123
+ font-size: 14px;
124
+ font-weight: 600;
125
+ padding: 4px 0;
126
+ }
127
+ }
128
+
129
+ #warning {
130
+ padding: 10px;
131
+ background: #FAE0D2;
132
+ color: #871821;
133
+ }
134
+
135
+ #info {
136
+ padding: 10px;
137
+ background: #c7f9ed;
138
+ color: #00344e;
139
+ }
140
+
141
+ `;
142
+
143
+ export const Header = styled.div`
144
+ display: flex;
145
+ flex-direction: column;
146
+
147
+ #button-upload {
148
+ margin-right: 10px;
149
+ }
150
+
151
+ .infos {
152
+ display: flex;
153
+ flex-direction: column;
154
+ padding: 14px 0;
155
+
156
+ h2 {
157
+ font-size: 20px
158
+ }
159
+
160
+ p {
161
+ font-size: 14px
162
+ }
163
+ }
164
+
165
+ .actions {
166
+ display: flex;
167
+ justify-content: space-between;
168
+ padding-bottom: 14px;
169
+
170
+ button {
171
+ background: #102693;
172
+ padding: 10px 20px;
173
+ color: white;
174
+ border-radius: 4px;
175
+
176
+ &:disabled {
177
+ cursor: not-allowed;
178
+ opacity: 0.7;
179
+ }
180
+ }
181
+ }
182
+
183
+ `
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import cc from 'classcat';
3
+
4
+ import * as S from './styles';
5
+
6
+ type LengthCounterProps = {
7
+ value?: string;
8
+ maxLength: number;
9
+ } & React.HTMLAttributes<HTMLDivElement>;
10
+
11
+ const LengthCounter: React.FC<LengthCounterProps> = ({
12
+ value = '',
13
+ maxLength,
14
+ ...rest
15
+ }) => {
16
+ const sanitizeValue = value?.replace?.(/{(.*?)}/g, '');
17
+ const length = sanitizeValue?.length || 0;
18
+
19
+ return (
20
+ <S.Container
21
+ className={cc({ error: length > maxLength })}
22
+ {...rest}
23
+ >
24
+ {`${length}/${maxLength}`}
25
+ </S.Container>
26
+ );
27
+ };
28
+
29
+ export default LengthCounter;
@@ -0,0 +1,11 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const Container = styled.span`
4
+ display: inline-block;
5
+ font-size: 12px;
6
+ font-weight: 400;
7
+
8
+ &.error {
9
+ color: var(--semantic-red-400-light);
10
+ }
11
+ `;
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import * as S from './styles';
3
+ import { IoClose } from 'react-icons/io5';
4
+
5
+ interface ModalProps {
6
+ title?: string;
7
+ children: React.ReactNode;
8
+ onClose: () => void;
9
+ }
10
+
11
+ const Modal = ({ title, children, onClose }: ModalProps) => {
12
+ return (
13
+ <S.Overlay>
14
+ <S.Container>
15
+ <S.Header>
16
+ {title && <h3>{title}</h3>}
17
+ <button onClick={onClose}>
18
+ <IoClose />
19
+ </button>
20
+ </S.Header>
21
+
22
+ <S.Content>
23
+ {children}
24
+ </S.Content>
25
+ </S.Container>
26
+ </S.Overlay>
27
+ );
28
+ };
29
+
30
+ export default Modal;
@@ -0,0 +1,52 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const Overlay = styled.div`
4
+ position: fixed;
5
+ top: 0;
6
+ left: 0;
7
+ width: 100%;
8
+ height: 100%;
9
+
10
+ background-color: rgba(151, 154, 165, 0.7);
11
+ z-index: 9999;
12
+
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ `;
17
+
18
+ export const Container = styled.div`
19
+ background: #ffffff;
20
+ width: 650px;
21
+ max-width: 95%;
22
+ border-radius: 5px;
23
+ `;
24
+
25
+ export const Header = styled.div`
26
+ display: flex;
27
+ justify-content: space-between;
28
+ align-items: center;
29
+
30
+ padding: 14px 16px;
31
+ border-bottom: 1px solid #dcdfe6;
32
+
33
+ h3 {
34
+ font-size: 16px;
35
+ margin: 0;
36
+ }
37
+
38
+ button {
39
+ background: transparent;
40
+ border: none;
41
+ cursor: pointer;
42
+
43
+ svg {
44
+ width: 18px;
45
+ height: 18px;
46
+ }
47
+ }
48
+ `;
49
+
50
+ export const Content = styled.div`
51
+ padding: 16px;
52
+ `;
@@ -0,0 +1,102 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import * as S from './styles';
3
+ import { getLanguage } from '../../utils/getLanguage'
4
+ import { Language } from '../../interface/Language';
5
+
6
+ interface Option {
7
+ label: string;
8
+ value: string;
9
+ }
10
+
11
+ interface MultiSelectProps {
12
+ options: Option[];
13
+ presset: string[];
14
+ setPresset: React.Dispatch<React.SetStateAction<string[]>>;
15
+ setOptions: React.Dispatch<React.SetStateAction<Option[]>>;
16
+ modal: boolean;
17
+ hasAddNewOptions: boolean;
18
+ language: keyof Record<'en' | 'pt-br' | 'es', Language>;
19
+ }
20
+
21
+ const MultiSelect: React.FC<MultiSelectProps> = ({ options, presset, setPresset, setOptions, modal, language, hasAddNewOptions }) => {
22
+ const [expanded, setExpanded] = useState(false);
23
+ const [newOption, setNewOption] = useState('');
24
+ const t = getLanguage(language)
25
+
26
+ useEffect(() => {
27
+ setExpanded(false)
28
+ }, [modal])
29
+
30
+ const handleCheckboxChange = (value: string) => {
31
+ setPresset(prevPresset => {
32
+ if (prevPresset.includes(value)) {
33
+ return prevPresset.filter(val => val !== value);
34
+ } else {
35
+ return [...prevPresset, value];
36
+ }
37
+ });
38
+ }
39
+
40
+ const showCheckboxes = () => {
41
+ setExpanded(!expanded);
42
+ }
43
+
44
+ const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
45
+ setNewOption(event.target.value);
46
+ }
47
+
48
+ const handleInputKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
49
+ if (event.key === 'Enter' && hasAddNewOptions) {
50
+ event.preventDefault();
51
+ const trimmedOption = newOption.trim();
52
+ if (trimmedOption !== '' && !options.some(option => option.value === trimmedOption)) {
53
+ setOptions(prevOptions => [...prevOptions, { label: trimmedOption, value: trimmedOption }]);
54
+ setPresset(prevPresset => [...prevPresset, trimmedOption]);
55
+ }
56
+ setNewOption('');
57
+ }
58
+ }
59
+
60
+ return (
61
+ <form>
62
+ <S.MultiSelectWrapper>
63
+ <S.SelectBox onClick={showCheckboxes}>
64
+ <select>
65
+ {options.map((option, index) => (
66
+ <option key={index} value={option.value}>{option.label}</option>
67
+ ))}
68
+ </select>
69
+ <S.OverSelect>
70
+ <S.SelectedValues>
71
+ {presset.map((value, index) => (
72
+ <S.SelectedValue key={index}>{value}</S.SelectedValue>
73
+ ))}
74
+ </S.SelectedValues>
75
+ <input
76
+ type="text"
77
+ placeholder={hasAddNewOptions ? t.buttons.addNewPresset : t.buttons.addPresset}
78
+ value={newOption}
79
+ onChange={handleInputChange}
80
+ onKeyPress={handleInputKeyPress}
81
+ />
82
+ </S.OverSelect>
83
+ </S.SelectBox>
84
+ <S.Checkboxes style={{ display: expanded ? 'block' : 'none' }}>
85
+ {options.map((option, index) => (
86
+ <S.CheckboxLabel key={index} htmlFor={option.value}>
87
+ <input
88
+ type="checkbox"
89
+ id={option.value}
90
+ checked={presset.includes(option.value)}
91
+ onChange={() => handleCheckboxChange(option.value)}
92
+ />
93
+ {option.label}
94
+ </S.CheckboxLabel>
95
+ ))}
96
+ </S.Checkboxes>
97
+ </S.MultiSelectWrapper>
98
+ </form>
99
+ );
100
+ }
101
+
102
+ export default MultiSelect;