· 6 years ago · Feb 19, 2020, 06:52 AM
1import PropTypes from 'prop-types';
2import React from 'react';
3import { useMutation } from '@apollo/react-hooks';
4import { useDispatch } from 'react-redux';
5import { useDropzone } from 'react-dropzone';
6import { useHistory } from 'react-router-dom';
7import nanoid from '@astraledo-web/common/utils/nanoid';
8import { useCurrentAbonentIdSelector } from '@astraledo-web/common/hooks';
9import { useNotification } from '@astral-frontend/notifications';
10import { DropOverlay } from '@astral-frontend/components';
11import { composeUpdaters, changeValue } from '@astraledo-web/common/utils';
12
13import { ADD_DRAFT_PACKAGE_MUTATION } from '../gql';
14import { DOCUMENTS_PACKAGE_STATUSES } from '../constants';
15import DocumentsAddUploadContext from '../AddUploadContext';
16import { UploadState, ImportRequest } from './models';
17import DraftsTotalCountQuery from './DraftsTotalCountQuery.graphql';
18
19const MAX_COUNT_OF_FILES_FOR_UPLOAD = 50;
20const MAX_SIZE_OF_EACH_FILE = 1024 * 1024 * 60; // 60 MB
21const MIN_SIZE_OF_EACH_FILE = 1; // 1 Byte
22
23const DocumentsAddUploadContextProvider = ({ children }) => {
24 const { enqueueErrorNotification } = useNotification();
25 const currentAbonentId = useCurrentAbonentIdSelector();
26 const history = useHistory();
27 const dispatch = useDispatch();
28 const [isDropOver, setIsDropOver] = React.useState(false);
29 const [uploadableDocuments, setUploadableDocuments] = React.useState({});
30 const [addDraftPackage] = useMutation(ADD_DRAFT_PACKAGE_MUTATION, {
31 update: composeUpdaters(
32 changeValue({
33 propName: 'totalCount',
34 query: DraftsTotalCountQuery,
35 variables: { params: { abonentId: currentAbonentId } },
36 valueUpdater: value => value + 1,
37 }),
38 ),
39 });
40 const [isDragging, setIsDragging] = React.useState(false);
41 const {
42 isDragActive,
43 open: openFileDialog,
44 getRootProps,
45 getInputProps,
46 } = useDropzone({
47 maxSize: MAX_SIZE_OF_EACH_FILE,
48 minSize: MIN_SIZE_OF_EACH_FILE,
49 onDrop: React.useCallback(
50 (acceptedFiles, rejectedFiles) => {
51 setIsDragging(false);
52 const uploadPossible =
53 acceptedFiles.length <= MAX_COUNT_OF_FILES_FOR_UPLOAD &&
54 acceptedFiles.length + Object.keys(uploadableDocuments).length <=
55 MAX_COUNT_OF_FILES_FOR_UPLOAD;
56
57 setIsDropOver(true);
58 history.push('/documents/add');
59 if (!uploadPossible) {
60 enqueueErrorNotification({
61 message: `Невозможно загрузить больше ${MAX_COUNT_OF_FILES_FOR_UPLOAD} файлов`,
62 });
63 } else {
64 acceptedFiles.forEach(async file => {
65 const key = nanoid();
66 const onUploadProgress = progressEvent => {
67 const percentCompleted = Math.round(
68 (progressEvent.loaded * 100) / progressEvent.total,
69 );
70
71 setUploadableDocuments(prevValue => {
72 if (!prevValue[key]) {
73 return prevValue;
74 }
75
76 const {
77 uploadState: { loading, error },
78 importRequest,
79 draftPackage,
80 } = prevValue[key];
81
82 return {
83 ...prevValue,
84 [key]: new ImportRequest(
85 new UploadState(loading, error, percentCompleted),
86 importRequest,
87 draftPackage,
88 ),
89 };
90 });
91 };
92
93 setUploadableDocuments(prevValue => ({
94 ...prevValue,
95 [key]: new ImportRequest(new UploadState(true, null, 0)),
96 }));
97
98 try {
99 await dispatch(async (_, getState, { api }) => {
100 const {
101 workspace: { abonentId: senderId },
102 } = getState();
103 const request = new FormData();
104 request.append('file', file);
105 request.append('senderId', senderId);
106
107 const { data: importRequest } = await api.http.post(
108 '/importRequests',
109 request,
110 {
111 onUploadProgress,
112 headers: {
113 'Content-Type': 'multipart/form-data',
114 },
115 },
116 );
117 const { shouldCreateDraftPackage } = DOCUMENTS_PACKAGE_STATUSES[
118 importRequest.status
119 ];
120 if (shouldCreateDraftPackage) {
121 const {
122 data: { addDraftPackage: draftPackage = {} } = {},
123 } = await addDraftPackage({
124 variables: { draftPackage: importRequest },
125 });
126
127 setUploadableDocuments(prevValue => {
128 if (!prevValue[key]) {
129 return prevValue;
130 }
131
132 const {
133 uploadState: { percentCompleted },
134 } = prevValue[key];
135
136 return {
137 ...prevValue,
138 [key]: new ImportRequest(
139 new UploadState(false, null, percentCompleted),
140 importRequest,
141 draftPackage,
142 ),
143 };
144 });
145 } else {
146 setUploadableDocuments(prevValue => {
147 if (!prevValue[key]) {
148 return prevValue;
149 }
150
151 const {
152 uploadState: { percentCompleted },
153 } = prevValue[key];
154
155 return {
156 ...prevValue,
157 [key]: new ImportRequest(
158 new UploadState(false, null, percentCompleted),
159 importRequest,
160 null,
161 ),
162 };
163 });
164 }
165 });
166 } catch (error) {
167 console.log(error);
168 const { message = 'Неизвестная ошибка' } =
169 error.response.data || error;
170 enqueueErrorNotification({ message });
171
172 setUploadableDocuments(prevValue => {
173 if (!prevValue[key]) {
174 return prevValue;
175 }
176
177 const {
178 uploadState: { percentCompleted },
179 importRequest,
180 draftPackage,
181 } = prevValue[key];
182
183 return {
184 ...prevValue,
185 [key]: new ImportRequest(
186 new UploadState(
187 false,
188 new Error(message),
189 percentCompleted,
190 ),
191 importRequest,
192 draftPackage,
193 ),
194 };
195 });
196 }
197 });
198 }
199 rejectedFiles.forEach(({ size, name }) => {
200 if (size > MAX_SIZE_OF_EACH_FILE) {
201 enqueueErrorNotification({
202 title: `Документ ${name} не загружен`,
203 message: `Размер файла превышает ${MAX_SIZE_OF_EACH_FILE /
204 1024 /
205 1024}Mb`,
206 });
207 } else if (size < MIN_SIZE_OF_EACH_FILE) {
208 enqueueErrorNotification({
209 title: `Документ ${name} не загружен`,
210 message: `Загрузка пустых файлов недопустима`,
211 });
212 } else {
213 enqueueErrorNotification({
214 message: `Документ ${name} не загружен по неизвестной причине`,
215 });
216 }
217 });
218 },
219 [uploadableDocuments],
220 ),
221 });
222
223 React.useEffect(() => {
224 document.addEventListener('dragover', event => {
225 event.preventDefault();
226 if (
227 event.dataTransfer.items.length &&
228 Object.values(event.dataTransfer.items).every(
229 ({ kind }) => kind === 'file',
230 )
231 ) {
232 setIsDragging(true);
233 }
234 });
235 }, []);
236
237 React.useEffect(() => {
238 setUploadableDocuments({});
239 }, [currentAbonentId]);
240
241 return (
242 <DocumentsAddUploadContext.Provider
243 value={{
244 isDropOver,
245 isDragActive,
246 uploadableDocuments,
247 getRootProps,
248 getInputProps,
249 openFileDialog,
250 setUploadableDocuments,
251 setIsDropOver,
252 }}
253 >
254 <input {...getInputProps()} />
255 <DropOverlay open={isDragging} rootProps={getRootProps()} />
256 {children}
257 </DocumentsAddUploadContext.Provider>
258 );
259};
260
261DocumentsAddUploadContextProvider.propTypes = {
262 children: PropTypes.node.isRequired,
263};
264
265export default DocumentsAddUploadContextProvider;