UNPKG

drf-react-by-schema

Version:

Components and Tools for building a React App having Django Rest Framework (DRF) as server

649 lines (648 loc) 23.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertToFormData = exports.slugify = exports.slugToCamelCase = exports.getPatternFormat = exports.isTmpId = exports.getTmpId = exports.errorProps = exports.populateValues = exports.getChoiceByValue = exports.emptyByType = void 0; exports.buildGenericYupValidationSchema = buildGenericYupValidationSchema; exports.reducer = reducer; exports.buildDateFormatBySchema = buildDateFormatBySchema; exports.mergeFilterItems = mergeFilterItems; exports.initViewColumns = initViewColumns; exports.shouldMemoUpdate = shouldMemoUpdate; exports.formatFileSize = formatFileSize; exports.getFieldsFromFieldsLayout = getFieldsFromFieldsLayout; exports.isFieldRequired = isFieldRequired; const dayjs_1 = __importDefault(require("dayjs")); const Yup = __importStar(require("yup")); const string_mask_1 = __importDefault(require("string-mask")); const emptyByType = (field, forDatabase = false) => { if (field.model_default) { return field.model_default; } switch (field.type) { case 'nested object': // emptyByType must be an empty object for the database, but must be null for the mui-autocomplete component. So I had to make this ugly hack here, when we're preparing to sendo to the database: return forDatabase ? {} : null; case 'field': if (field.child) { return []; } return forDatabase ? undefined : null; case 'list': return []; case 'string': case 'email': return ''; case 'integer': return 0; case 'array': return []; case 'boolean': return false; default: return null; } }; exports.emptyByType = emptyByType; const getChoiceByValue = (value, choices) => { if (!choices) { return null; } for (const choice of choices) { if (choice.value === value) { return choice.display_name; } } }; exports.getChoiceByValue = getChoiceByValue; const populateValues = ({ data, schema }) => { var _a; const values = {}; for (const [key, field] of Object.entries(schema)) { if (key === 'id' && (0, exports.isTmpId)(data[key])) { continue; } if (!data[key] && data[key] !== false && data[key] !== 0) { values[key] = (0, exports.emptyByType)(field); continue; } if (field.type === 'field' && ((_a = field.child) === null || _a === void 0 ? void 0 : _a.children)) { if (Array.isArray(data[key])) { const arValues = []; for (const row of data[key]) { const value = (0, exports.populateValues)({ data: row, schema: field.child.children, }); arValues.push(value); } values[key] = arValues; continue; } values[key] = (0, exports.populateValues)({ data: data[key], schema: field.child.children, }); continue; } if (field.type === 'choice') { values[key] = data[key] ? { value: data[key], display_name: (0, exports.getChoiceByValue)(data[key], field.choices), } : (0, exports.emptyByType)(field); continue; } values[key] = data[key]; } return values; }; exports.populateValues = populateValues; const getYupValidator = (type) => { let yupFunc; try { switch (type) { case 'slug': yupFunc = Yup.string(); break; case 'email': yupFunc = Yup.string().email('Este campo deve ser um e-mail válido.'); break; case 'integer': yupFunc = Yup.number().integer('Este campo deve ser um número inteiro'); break; case 'choice': yupFunc = Yup.object(); break; case 'list': yupFunc = Yup.object(); break; case 'field': yupFunc = Yup.mixed(); break; case 'nested object': yupFunc = Yup.object(); break; case 'date': yupFunc = Yup.date(); break; case 'string': yupFunc = Yup.string(); break; case 'number': case 'decimal': yupFunc = Yup.number(); break; case 'boolean': yupFunc = Yup.bool(); break; case 'array': yupFunc = Yup.array(); break; case 'object': case 'image': case 'image upload': case 'file': case 'file upload': yupFunc = Yup.object(); break; default: yupFunc = Yup.string(); break; } // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { yupFunc = Yup.string(); } return yupFunc.transform((v) => (v === '' ? null : v)).nullable(); }; function buildGenericYupValidationSchema({ data, schema, many = false, skipFields = [], extraValidators, uiFields, }) { var _a; const yupValidator = {}; for (const entry of Object.entries(schema)) { const [field, fieldSchema] = entry; if (!data || !(field in data) || field === 'id' || skipFields.includes(field)) { continue; } // OneToMany or ManyToMany: if (fieldSchema.type === 'field' && ((_a = fieldSchema.child) === null || _a === void 0 ? void 0 : _a.children)) { yupValidator[field] = extraValidators && field in extraValidators ? extraValidators[field] : buildGenericYupValidationSchema({ schema: fieldSchema.child.children, many: true, data: data[field], uiFields, }); continue; } // Nested Object: if (fieldSchema.type === 'nested object' && fieldSchema.children) { // yupValidator[key] = buildGenericYupValidationSchema({ // schema: field.children, // many: false, // data: data[key], // extraValidators: key in extraValidators ? extraValidators[key] : {} // }); // if (!field.model_required) { // yupValidator[key] = yupValidator[key].nullable(); // } // continue; } yupValidator[field] = extraValidators && field in extraValidators ? extraValidators[field] : getYupValidator(fieldSchema.type); if (isFieldRequired(fieldSchema, (uiFields || []).includes(field))) { yupValidator[field] = yupValidator[field].required('Este campo é obrigatório'); } if (fieldSchema.max_length && fieldSchema.type === 'string') { yupValidator[field] = yupValidator[field].max(parseInt(fieldSchema.max_length), `Este campo só pode ter no máximo ${fieldSchema.max_length} caracteres`); } if (fieldSchema.max_digits && fieldSchema.type === 'decimal') { const maxDigits = fieldSchema.max_digits; yupValidator[field] = yupValidator[field].test('len', `Este número pode ter no máximo ${maxDigits} dígitos`, (val) => { if (!val) { return true; } return val.toString().length <= maxDigits; }); } if (!fieldSchema.read_only && fieldSchema.validators_regex) { for (const validator of fieldSchema.validators_regex) { yupValidator[field] = yupValidator[field].matches(validator.regex, validator.message); } } } return many ? Yup.array().of(Yup.object().shape(yupValidator)) : Yup.object().shape(yupValidator); } const errorProps = ({ errors, fieldKey, fieldKeyProp, index, }) => { let error; let helperText; if (index) { const hasErrors = errors && errors[fieldKey] && errors[fieldKey][index] && Boolean(errors[fieldKey][index][fieldKeyProp]); error = hasErrors; helperText = hasErrors ? errors[fieldKey][index][fieldKeyProp].message : null; return { error, helperText }; } const hasErrors = errors && errors[fieldKey] && Boolean(errors[fieldKey][fieldKeyProp]); error = hasErrors; helperText = hasErrors ? errors[fieldKey][fieldKeyProp].message : null; return { error, helperText }; }; exports.errorProps = errorProps; const getTmpId = () => { return 'tmp' + Math.floor(Math.random() * 1000000); }; exports.getTmpId = getTmpId; const isTmpId = (id) => { if (!id) { return true; } return id === 'novo' || id.toString().slice(0, 3) === 'tmp'; }; exports.isTmpId = isTmpId; function reducer(state, newState) { if (newState === null) { return null; } if (state === null) { return newState; } return Object.assign(Object.assign({}, state), newState); } const getPatternFormat = (type) => { let format = ''; switch (type) { case 'telefone': case 'fone': case 'phone': case 'contact': case 'contato': format = '(##)#####-####'; break; case 'cpf': format = '###.###.###-##'; break; case 'cnpj': format = '##.###.###/####-##'; break; case 'cep': format = '##.###-###'; break; } return format; }; exports.getPatternFormat = getPatternFormat; function buildDateFormatBySchema(dateViews) { const defaultFormat = 'DD/MM/YYYY'; if (!dateViews) { return defaultFormat; } const hasDay = dateViews.includes('day'); const hasMonth = dateViews.includes('month'); const hasYear = dateViews.includes('year'); if (hasDay && hasMonth && hasYear) { return defaultFormat; } if (!hasDay && hasMonth && hasYear) { return 'MM/YYYY'; } if (!hasDay && !hasMonth && hasYear) { return 'YYYY'; } if (!hasDay && hasMonth && !hasYear) { return 'MM'; } if (hasDay && !hasMonth && !hasYear) { return 'DD'; } return defaultFormat; } const slugToCamelCase = (str, includeFirst = false) => { if (!str) { return ''; } const ret = str.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); }); if (includeFirst) { return ret[0].toUpperCase() + ret.slice(1); } return ret; }; exports.slugToCamelCase = slugToCamelCase; const slugify = (text) => { if (!text) { return ''; } return text .toString() .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .toLowerCase() .trim() .replace(/\s+/g, '-') .replace(/[^\w-]+/g, '') .replace(/--+/g, '-'); }; exports.slugify = slugify; function mergeFilterItems(defaultFilter, filter) { if (!filter && defaultFilter) { return defaultFilter; } if (!defaultFilter && filter) { return filter; } if (filter && defaultFilter) { const items = filter.items; const defaultItems = defaultFilter.items; const mergedItems = defaultItems.map((defaultItem) => { const itemFound = items.find((item) => item.columnField === defaultItem.columnField); if (itemFound) { return itemFound; } return defaultItem; }); const unMergedItems = items.filter((item) => !defaultItems.map((defaultItem) => defaultItem.columnField).includes(item.columnField)); return Object.assign(Object.assign({}, filter), { items: [...mergedItems, ...unMergedItems] }); } return undefined; } function initViewColumns(_a) { return __awaiter(this, arguments, void 0, function* ({ schema, columns, customColumnOperations, }) { const colsPromises = columns.map((column) => __awaiter(this, void 0, void 0, function* () { const field = column.field; if (!schema[field]) { return column; } const fieldSchema = schema[field]; // const isIndexField = indexField !== '' && field === indexField; // let column = col; // if (isIndexField) { // col.isIndexField = true; // } switch (fieldSchema.type) { case 'date': column.type = 'date'; const dateFormat = buildDateFormatBySchema(fieldSchema.date_views); column.valueFormatter = (params) => params.value ? (0, dayjs_1.default)(params.value).format(dateFormat) : ''; break; case 'datetime': column.type = 'dateTime'; column.valueFormatter = (params) => params.value ? (0, dayjs_1.default)(params.value).format('DD/MM/YYYY HH:mm') : ''; break; case 'nested object': column.valueFormatter = (params) => { return !params.value ? '' : params.value.label; }; break; case 'field': column.valueFormatter = (params) => { return !params.value || !Array.isArray(params.value) ? '' : params.value.map((val) => val.label).join(', '); }; break; case 'choice': column.valueFormatter = (params) => { return !params.value ? '' : params.value.display_name; }; break; case 'list': column.valueFormatter = (params) => { return !params.value || !Array.isArray(params.value) ? '' : params.value.map((val) => val.value.display_name).join(', '); }; break; case 'boolean': column.valueFormatter = (params) => { return params.value ? 'Sim' : 'Não'; }; break; case 'decimal': case 'float': const decimalScale = fieldSchema.decimal_places || 2; column.type = 'number'; column.valueFormatter = (params) => { return !params.value ? '' : parseFloat(params.value).toLocaleString('pt-BR', { minimumFractionDigits: decimalScale, maximumFractionDigits: decimalScale, }); }; break; case 'number': case 'integer': column.type = 'number'; if (fieldSchema.pattern_format) { column.patternFormat = fieldSchema.pattern_format; } break; case 'file upload': case 'image upload': column.valueFormatter = (params) => { return !params.value ? '' : `${params.value.name.split('/').pop()} (${formatFileSize(params.value.size)})`; }; default: if (fieldSchema.pattern_format) { column.patternFormat = fieldSchema.pattern_format; } } // format numbers: if (column.patternFormat) { column.valueFormatter = (params) => { if (!params.value || typeof params.value !== 'string') { return params.value; } const formattedValue = new string_mask_1.default(column.patternFormat, {}).apply(params.value); return formattedValue; }; } // Custom column operations: if (customColumnOperations) { column = yield customColumnOperations(column); } // Finally! return column; })); const finalCols = yield Promise.all(colsPromises); return finalCols; }); } function deepEqual(a, b) { if (!a && !b) { return true; } if (a === b) { return true; } if (typeof a === 'function' && typeof b === 'function') { if (a.toString() !== b.toString()) { return false; } return true; } if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') { return false; } // Handle arrays if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { return false; } return a.every((val, i) => deepEqual(val, b[i])); } // Handle objects const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) { return false; } return keysA.every((key) => { const valA = a[key]; const valB = b[key]; return deepEqual(valA, valB); }); } function shouldMemoUpdate(prevProps, nextProps) { const propKeys = new Set([...Object.keys(prevProps), ...Object.keys(nextProps)]); for (const key of propKeys) { if (typeof key === 'string' && key.startsWith('_')) continue; const prevVal = prevProps[key]; const nextVal = nextProps[key]; // Fast path for identical references if (prevVal === nextVal) continue; // Handle null/undefined mismatches if (prevVal == null || nextVal == null) { return false; } // Special handling for functions if (typeof prevVal === 'function' && typeof nextVal === 'function') { if (prevVal.toString() !== nextVal.toString()) { return false; } continue; } // Deep compare objects if (typeof prevVal === 'object' && typeof nextVal === 'object') { if (!deepEqual(prevVal, nextVal)) { return false; } continue; } // Final comparison for primitives if (prevVal !== nextVal) { return false; } } return true; } const convertToFormData = (data) => { const formData = new FormData(); Object.entries(data).forEach(([key, value]) => { if (value === null) { formData.append(key, ''); // Handle null values } else if (Array.isArray(value)) { value.forEach((item) => { if (typeof item === 'object') { formData.append(`${key}[]`, JSON.stringify(item)); } }); } else if (typeof value === 'object') { formData.append(key, 'file' in value ? value.file : JSON.stringify(value)); } else { formData.append(key, value); } }); return formData; }; exports.convertToFormData = convertToFormData; function formatFileSize(bytes) { if (bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB']; const k = 1024; const dm = 1; const i = Math.floor(Math.log(bytes) / Math.log(k)); const value = bytes / Math.pow(k, i); const formattedValue = value.toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: dm, }); return `${formattedValue} ${units[i]}`; } function getFieldsFromFieldsLayout(fieldsLayout) { const fields = []; for (const section of fieldsLayout) { if (!section.rows) { continue; } for (const row of section.rows) { if (typeof row === 'string') { fields.push(row); continue; } if (Array.isArray(row)) { for (const field of row) { if (typeof field === 'string') { fields.push(field); continue; } } continue; } } } return fields; } function isFieldRequired(fieldSchema, isInForm) { if (fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.required) { return true; } if (fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.model_required) { return true; } if ((fieldSchema === null || fieldSchema === void 0 ? void 0 : fieldSchema.ui_required) && (typeof isInForm === 'undefined' || isInForm === true)) { return true; } return false; }