drf-react-by-schema 0.0.2 → 0.1.0

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.
package/.eslintrc.js ADDED
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ parser: '@typescript-eslint/parser',
3
+ extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
4
+ parserOptions: {
5
+ sourceType: 'module',
6
+ },
7
+ rules: {
8
+ 'prettier/prettier': 'error',
9
+ },
10
+ plugins: ['@typescript-eslint', 'prettier'],
11
+ }
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DataGridBySchemaEditable = void 0;
7
+ const DataGridBySchemaEditable_1 = __importDefault(require("./components/DataGridBySchemaEditable"));
8
+ exports.DataGridBySchemaEditable = DataGridBySchemaEditable_1.default;
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "drf-react-by-schema",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "Tools for building a React App having Django Rest Framework (DRF) as server",
5
- "main": "index.js",
5
+ "main": "dist/index.js",
6
6
  "scripts": {
7
- "test": "echo \"There are (still) no tests in this package\""
7
+ "test": "echo \"There are (still) no tests in this package\"",
8
+ "build": "tsc"
8
9
  },
9
10
  "repository": {
10
11
  "type": "git",
@@ -16,9 +17,36 @@
16
17
  "django"
17
18
  ],
18
19
  "author": "Coletivo EITA",
19
- "license": "ISC",
20
+ "license": "MIT",
20
21
  "bugs": {
21
22
  "url": "https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-js/issues"
22
23
  },
23
- "homepage": "https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-js#readme"
24
+ "homepage": "https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-js#readme",
25
+ "devDependencies": {
26
+ "@types/react": "^18.0.26",
27
+ "@typescript-eslint/eslint-plugin": "^5.47.0",
28
+ "@typescript-eslint/parser": "^5.47.0",
29
+ "eslint": "^8.30.0",
30
+ "eslint-config-prettier": "^8.5.0",
31
+ "eslint-plugin-prettier": "^4.2.1",
32
+ "react": "^18.2.0",
33
+ "react-dom": "^18.2.0",
34
+ "typescript": "^4.9.4"
35
+ },
36
+ "dependencies": {
37
+ "@mui/icons-material": "^5.11.0",
38
+ "@mui/lab": "^5.0.0-alpha.112",
39
+ "@mui/material": "^5.11.0",
40
+ "@mui/x-data-grid": "^5.17.14",
41
+ "@mui/x-date-pickers": "^5.0.11",
42
+ "@types/jest": "^29.2.4",
43
+ "@types/node": "^18.11.15",
44
+ "@types/react-dom": "^18.0.9",
45
+ "axios": "^1.2.1",
46
+ "moment": "^2.29.4",
47
+ "react-number-format": "^5.1.2",
48
+ "react-router-dom": "^6.4.5",
49
+ "string-mask": "^0.3.0",
50
+ "yup": "^0.32.11"
51
+ }
24
52
  }
package/src/api.ts ADDED
@@ -0,0 +1,526 @@
1
+ import axios, { AxiosError, AxiosResponse } from 'axios';
2
+ import {
3
+ isTmpId,
4
+ emptyByType,
5
+ getChoiceByValue,
6
+ Field,
7
+ Item,
8
+ Schema
9
+ } from './utils';
10
+
11
+ const moment = require('moment');
12
+
13
+ type Id = string | number | null;
14
+ interface TargetApiParams {
15
+ path:string,
16
+ data:Item,
17
+ id:Id
18
+ };
19
+ interface TargetApiParamsOptionalId {
20
+ path:string,
21
+ data:Item,
22
+ id?:Id
23
+ };
24
+ interface Constants {
25
+ API: Record<string, string>
26
+ };
27
+ const CONSTANTS:Constants = {
28
+ API: {
29
+ api: '',
30
+ JSONSchema: '',
31
+ getToken: ''
32
+ }
33
+ };
34
+ const getOptions = async (path:string) => {
35
+ const url = `${CONSTANTS.API.api}/${path}`;
36
+ try {
37
+ const { data } = await axios.options(url);
38
+ return data;
39
+ } catch (e) {
40
+ if (e instanceof AxiosError && e.response?.status === 401) {
41
+ const isRefreshed = await refreshToken();
42
+ if (!isRefreshed) {
43
+ console.log('Token expirou! Deve-se fazer login de novo');
44
+ return false;
45
+ }
46
+ try {
47
+ const { data } = await axios.options(url);
48
+ return data;
49
+ } catch (e) {
50
+ console.log(`Error fetching options from ${url}`, e);
51
+ return false;
52
+ }
53
+ }
54
+ console.log(`Error fetching options from ${url}`, e);
55
+ return false;
56
+ }
57
+ };
58
+
59
+ const getSchema = async (path:string) => {
60
+ const options = await getOptions(path);
61
+ if (!options || !options.actions || !options.actions.POST) {
62
+ return false;
63
+ }
64
+
65
+ // Special default value of "currentUser":
66
+ let usuaria;
67
+ const postActions:Record<string, Field> = options.actions.POST;
68
+ for (const [key, field] of Object.entries(postActions)) {
69
+ if (field.model_default === 'currentUser') {
70
+ if (!usuaria) {
71
+ usuaria = await isLoggedIn();
72
+ }
73
+ if (usuaria) {
74
+ options.actions.POST[key].model_default = {
75
+ id: usuaria.id,
76
+ label: usuaria.first_name
77
+ };
78
+ }
79
+ }
80
+ }
81
+
82
+ return options.actions.POST;
83
+ };
84
+
85
+ const getData = async (
86
+ path:string,
87
+ route:string = 'api'
88
+ ) => {
89
+ const url = `${CONSTANTS.API[route]}/${path}`;
90
+ try {
91
+ const { data } = await axios.get(url);
92
+ return data;
93
+ } catch (e) {
94
+ if (e instanceof AxiosError && e.response?.status === 401) {
95
+ const isRefreshed = await refreshToken();
96
+ if (!isRefreshed) {
97
+ console.log('Token expirou! Deve-se fazer login de novo');
98
+ return false;
99
+ }
100
+ try {
101
+ const { data } = await axios.get(url);
102
+ return data;
103
+ } catch (e) {
104
+ console.log(`Error fetching data from ${url}`, e);
105
+ return false;
106
+ }
107
+ }
108
+ console.log(`Error fetching data from ${url}`, e);
109
+ return false;
110
+ }
111
+ };
112
+
113
+ export const updateData = async ({ path, data, id }: TargetApiParams) => {
114
+ const url = `${CONSTANTS.API.api}/${path}/${id}/`;
115
+ try {
116
+ await axios.put(url, data);
117
+ return id;
118
+ } catch (e) {
119
+ if (e instanceof AxiosError && e.response?.status === 401) {
120
+ const isRefreshed = await refreshToken();
121
+ if (!isRefreshed) {
122
+ console.log('Token expirou! Deve-se fazer login de novo');
123
+ return false;
124
+ }
125
+ try {
126
+ await axios.put(url, data);
127
+ return true;
128
+ } catch (e) {
129
+ console.log(`Error updating data at ${url}`, data, e);
130
+ return false;
131
+ }
132
+ }
133
+ const err = e as AxiosError;
134
+ console.log(`Error updating data at ${url}`, data, err.response?.data);
135
+ return ({ errors: err.response?.data } as unknown) as AxiosResponse;
136
+ }
137
+ };
138
+
139
+ export const partialUpdateData = async ({ path, data, id }: TargetApiParams) => {
140
+ const url = `${CONSTANTS.API.api}/${path}/${id}/`;
141
+ try {
142
+ await axios.patch(url, data);
143
+ // DEBUG console.log({ path, data, id });
144
+ return id;
145
+ } catch (e) {
146
+ if (e instanceof AxiosError && e.response?.status === 401) {
147
+ const isRefreshed = await refreshToken();
148
+ if (!isRefreshed) {
149
+ console.log('Token expirou! Deve-se fazer login de novo');
150
+ return false;
151
+ }
152
+ try {
153
+ await axios.patch(url, data);
154
+ return true;
155
+ } catch (e) {
156
+ console.log(`Error partial updating data at ${url}`, data, e);
157
+ return false;
158
+ }
159
+ }
160
+ const err = e as AxiosError;
161
+ console.log(`Error partial updating data at ${url}`, data, err.response?.data);
162
+ return ({ errors: err.response?.data } as unknown) as AxiosResponse;
163
+ }
164
+ };
165
+
166
+ export const createData = async ({ path, data }: Omit<TargetApiParams, 'id'>) => {
167
+ const url = `${CONSTANTS.API.api}/${path}/`;
168
+ try {
169
+ const ret = await axios.post(url, data);
170
+ return ret;
171
+ } catch (e) {
172
+ if (e instanceof AxiosError && e.response?.status === 401) {
173
+ const isRefreshed = await refreshToken();
174
+ if (!isRefreshed) {
175
+ console.log('Token expirou! Deve-se fazer login de novo');
176
+ return false;
177
+ }
178
+ try {
179
+ const ret = await axios.post(url, data);
180
+ return ret;
181
+ } catch (e) {
182
+ console.log(`Error creating data at ${url}`, data, e);
183
+ return false;
184
+ }
185
+ }
186
+ const err = e as AxiosError;
187
+ console.log(`Error creating data at ${url}`, data, err.response?.data);
188
+ return ({ errors: err.response?.data } as unknown) as AxiosResponse;
189
+ }
190
+ };
191
+
192
+ export const deleteData = async (path:string, id:Id) => {
193
+ const url = `${CONSTANTS.API.api}/${path}/${id}`;
194
+ try {
195
+ await axios.delete(url);
196
+ return true;
197
+ } catch (e) {
198
+ if (e instanceof AxiosError && e.response?.status === 401) {
199
+ const isRefreshed = await refreshToken();
200
+ if (!isRefreshed) {
201
+ console.log('Token expirou! Deve-se fazer login de novo');
202
+ return false;
203
+ }
204
+ try {
205
+ await axios.delete(url);
206
+ return true;
207
+ } catch (e) {
208
+ console.log(`Error deleting data from ${url}`, e);
209
+ return false;
210
+ }
211
+ }
212
+ console.log(`Error deleting data from ${url}`, e);
213
+ return false;
214
+ }
215
+ };
216
+
217
+ export const createOrUpdateData = async ({ path, data, id }: TargetApiParamsOptionalId) => {
218
+ if (isTmpId(id)) {
219
+ id = null;
220
+ }
221
+ if (id) {
222
+ const responseUpdate = await updateData({ path, data, id });
223
+ return responseUpdate;
224
+ }
225
+
226
+ const responseCreate = await createData({ path, data });
227
+ if (!responseCreate || Object.prototype.hasOwnProperty.call(responseCreate, 'errors')) {
228
+ return responseCreate;
229
+ }
230
+
231
+ const responseUpdate = await updateData({ path, data, id: responseCreate.data.id });
232
+ return responseUpdate;
233
+ };
234
+
235
+ const prepareDataBySchema = ({
236
+ data,
237
+ schema,
238
+ parentIsField = false
239
+ }: {
240
+ data:Item,
241
+ schema:Schema,
242
+ parentIsField?:boolean
243
+ }) => {
244
+ // console.log('Entered prepareDataBySchema', schema);
245
+ const dbData:Item = {};
246
+ for (const [key, field] of Object.entries(schema)) {
247
+ // console.log(key, field, data);
248
+ if (!(key in data) || (key === 'id' && isTmpId(data[key]))) {
249
+ continue;
250
+ }
251
+ if (!data[key]) {
252
+ dbData[key] = emptyByType(field, true);
253
+ continue;
254
+ }
255
+ // console.log({ key, data: data[key] });
256
+
257
+ // OneToMany or ManyToMany relations:
258
+ if (field.type === 'field' && field.child) {
259
+ const dataArr:Item[] = data[key];
260
+ dbData[key] = dataArr.map(row => {
261
+ return (field.child?.children)
262
+ ? prepareDataBySchema({
263
+ data: row,
264
+ schema: field.child.children,
265
+ parentIsField: true
266
+ })
267
+ : row;
268
+ });
269
+ continue;
270
+ }
271
+
272
+ // Nested Object:
273
+ if (field.type === 'nested object' && field.children && typeof data[key] === 'object') {
274
+ dbData[key] = (isTmpId(data[key].id))
275
+ ? prepareDataBySchema({
276
+ data: data[key] as Item,
277
+ schema: field.children
278
+ })
279
+ : data[key].id;
280
+ continue;
281
+ }
282
+
283
+ // Choices:
284
+ if (field.type === 'choice') {
285
+ dbData[key] = data[key].value;
286
+ continue;
287
+ }
288
+
289
+ // Date:
290
+ if (field.type === 'date') {
291
+ const date = moment.moment(data[key]);
292
+ dbData[key] = date.format('YYYY-MM-DD');
293
+ continue;
294
+ }
295
+
296
+ // DateTime:
297
+ if (field.type === 'datetime') {
298
+ const date = moment.moment(data[key]);
299
+ dbData[key] = date.format('YYYY-MM-DDTHH:mm');
300
+ continue;
301
+ }
302
+
303
+ // Default:
304
+ dbData[key] = data[key];
305
+ }
306
+ return dbData;
307
+ };
308
+
309
+ export const updateDataBySchema = async ({
310
+ model,
311
+ modelObjectId,
312
+ data,
313
+ schema,
314
+ path = null
315
+ }: {
316
+ model:string,
317
+ modelObjectId:Id,
318
+ data:Item,
319
+ schema:Schema,
320
+ path:string | null
321
+ }) => {
322
+ // console.log({
323
+ // model,
324
+ // modelObjectId,
325
+ // data,
326
+ // schema
327
+ // });
328
+ const dbData:Item = prepareDataBySchema({ data, schema });
329
+
330
+ if (!path) {
331
+ path = model;
332
+ }
333
+ // DEBUG console.log({ model, modelObjectId, path, data, dbData });
334
+ const response = await createOrUpdateData({
335
+ path,
336
+ data: dbData,
337
+ id: modelObjectId
338
+ });
339
+
340
+ if (typeof response !== 'object' && parseInt(response as string)) {
341
+ return response;
342
+ }
343
+
344
+ console.log('ERRO MAIN', response);
345
+ return response;
346
+ };
347
+
348
+ export const addExistingRelatedModel = async ({
349
+ model,
350
+ id,
351
+ data
352
+ }: {
353
+ model:string,
354
+ id:Id,
355
+ data:Item
356
+ }) => {
357
+ const response = await partialUpdateData({ path: model, data, id });
358
+ // DEBUG console.log({ model, id, data, response });
359
+ return response;
360
+ };
361
+
362
+ const getDataGridColumns = (
363
+ schema:Schema,
364
+ columnFields:string[] = [],
365
+ hiddenFields:string[] = [],
366
+ creatableFields:string[] = []
367
+ ) => {
368
+ if (!schema) {
369
+ return false;
370
+ }
371
+ columnFields = (!columnFields || columnFields.length === 0)
372
+ ? Object.keys(schema)
373
+ : columnFields.filter(field => {
374
+ return Object.prototype.hasOwnProperty.call(schema, field);
375
+ });
376
+
377
+ return columnFields.map(field => {
378
+ const column = {
379
+ field,
380
+ headerName: schema[field].label,
381
+ hide: (hiddenFields.includes(field)),
382
+ creatable: (creatableFields.includes(field)),
383
+ width: 100
384
+ };
385
+ return column;
386
+ });
387
+ };
388
+
389
+ export const getAutoComplete = async (model:string) => {
390
+ const data = await getData(model, 'autocomplete');
391
+ return data;
392
+ };
393
+
394
+ export const getAssessoriaRelatedObjects = async (id:Id) => {
395
+ const data = await getData(`assessoria/${id}/related_objects/`);
396
+ return data;
397
+ };
398
+
399
+ export const getJSONSchema = async ({
400
+ model,
401
+ id = 'create'
402
+ }: {
403
+ model:string,
404
+ id?:Id
405
+ }) => {
406
+ const url = `${CONSTANTS.API.JSONSchema}/${model}/${id}/`;
407
+ try {
408
+ const { data } = await axios.get(url);
409
+ return data;
410
+ } catch (e) {
411
+ if (e instanceof AxiosError && e.response?.status === 401) {
412
+ const isRefreshed = await refreshToken();
413
+ if (!isRefreshed) {
414
+ console.log('Token expirou! Deve-se fazer login de novo');
415
+ return false;
416
+ }
417
+ try {
418
+ const { data } = await axios.get(url);
419
+ return data;
420
+ } catch (e) {
421
+ console.log(`Error fetching JSONSchema data from ${url}`, e);
422
+ return false;
423
+ }
424
+ }
425
+ console.log(`Error fetching JSONSchema data from ${url}`, e);
426
+ return false;
427
+ }
428
+ };
429
+
430
+ export const createOrUpdateJSONSchema = async ({
431
+ model,
432
+ id = 'create',
433
+ formData
434
+ }: {
435
+ model:string,
436
+ id?:Id,
437
+ formData:Item
438
+ }) => {
439
+ let url = `${CONSTANTS.API.JSONSchema}/${model}/`;
440
+ if (id !== 'create') {
441
+ url += `${id}/`;
442
+ }
443
+ try {
444
+ const { data } = (id === 'create')
445
+ ? await axios.post(url, formData)
446
+ : await axios.patch(url, formData);
447
+ return data;
448
+ } catch (e) {
449
+ if (e instanceof AxiosError && e.response?.status === 401) {
450
+ const isRefreshed = await refreshToken();
451
+ if (!isRefreshed) {
452
+ console.log('Token expirou! Deve-se fazer login de novo');
453
+ return { errors: 'Token expirou! Deve-se fazer login de novo!' };
454
+ }
455
+ try {
456
+ const { data } = (id === 'create')
457
+ ? await axios.post(url, formData)
458
+ : await axios.patch(url, formData);
459
+ return data;
460
+ } catch (e) {
461
+ console.log(`Error partially updating or creating JSONSchema data from ${url}`, e);
462
+ return { errors: 'Erro ao salvar alterações em item!' };
463
+ }
464
+ }
465
+ console.log(`Error partially updating or creating JSONSchema data from ${url}`, e);
466
+ return { errors: 'Erro ao salvar alterações em item!' };
467
+ }
468
+ };
469
+
470
+ export const loginByPayload = async (payload:Item) => {
471
+ const url = CONSTANTS.API.getToken;
472
+ try {
473
+ const { data } = await axios.post(url, payload);
474
+ localStorage.setItem('refreshToken', data.refresh);
475
+ setAuthToken(data.access);
476
+ return true;
477
+ } catch (e) {
478
+ console.log('Erro no loginByPayload!', e);
479
+ setAuthToken(null);
480
+ return false;
481
+ }
482
+ };
483
+
484
+ export const setAuthToken = (token:string | null) => {
485
+ if (token) {
486
+ localStorage.setItem('token', token);
487
+ axios.defaults.headers.common.Authorization = `Bearer ${token}`;
488
+ return;
489
+ }
490
+ localStorage.removeItem('token');
491
+ localStorage.removeItem('refreshToken');
492
+ delete axios.defaults.headers.common.Authorization;
493
+ };
494
+
495
+ const refreshToken = async () => {
496
+ const refreshToken = localStorage.getItem('refreshToken');
497
+ // console.log('entrou refreshToken', refreshToken);
498
+ setAuthToken(null);
499
+ if (!refreshToken) {
500
+ return false;
501
+ }
502
+ try {
503
+ const { data } = await axios.post(`${CONSTANTS.API.refreshToken}`, {
504
+ refresh: refreshToken
505
+ });
506
+ setAuthToken(data.access);
507
+ return true;
508
+ } catch (e) {
509
+ console.log('Failed refreshing token', e);
510
+ return false;
511
+ }
512
+ };
513
+
514
+ export const isLoggedIn = async () => {
515
+ const token = localStorage.getItem('token');
516
+ setAuthToken(token);
517
+ if (!token) {
518
+ return false;
519
+ }
520
+ const usuaria = await getData('minhaconta');
521
+ if (!usuaria) {
522
+ console.log('Erro ao recuperar dados de usuária!');
523
+ return false;
524
+ }
525
+ return usuaria;
526
+ };