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
JavaScript
"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;
}