819 lines
30 KiB
TypeScript
819 lines
30 KiB
TypeScript
import { Entity, Property, RowMedia, RowValue, RowValueMultiple, RowValueRange } from "@toolctrl/db/prisma-browser";
|
|
import { Decimal } from "decimal.js";
|
|
import { RowValueDto } from "~/application/dtos/entities/RowValueDto";
|
|
import { MediaDto } from "~/application/dtos/entities/MediaDto";
|
|
import { PropertyType } from "~/application/enums/entities/PropertyType";
|
|
import { EntityWithDetails, EntityWithProperties, PropertyWithDetails } from "../db/entities/entities.db.server";
|
|
import NumberUtils, { NumberFormatType } from "../shared/NumberUtils";
|
|
import { validatePropertyValue, validatePropertyValue_Media, validatePropertyValue_Multiple } from "./PropertyHelper";
|
|
import i18next, { TFunction, t as i18nT } from "i18next";
|
|
import { RowValueWithDetails, RowWithValues } from "../db/entities/rows.db.server";
|
|
import { PropertyAttributeName } from "~/application/enums/entities/PropertyAttributeName";
|
|
import PropertyOptionValueBadge from "~/components/entities/properties/PropertyOptionValueBadge";
|
|
import RowMediaCell from "~/components/entities/rows/cells/RowMediaCell";
|
|
import RowBooleanCell, { getBooleanAsStringValue } from "~/components/entities/rows/cells/RowBooleanCell";
|
|
import { BooleanFormatType } from "../shared/BooleanUtils";
|
|
import RowNumberCell, { getNumberAsStringValue } from "~/components/entities/rows/cells/RowNumberCell";
|
|
import RowDateCell, { getDateAsString } from "~/components/entities/rows/cells/RowDateCell";
|
|
import { DateFormatType } from "../shared/DateUtils";
|
|
import { RowValueMultipleDto } from "~/application/dtos/entities/RowValueMultipleDto";
|
|
import PropertyMultipleValueBadge from "~/components/entities/properties/PropertyMultipleValueBadge";
|
|
import { RowValueRangeDto } from "~/application/dtos/entities/RowValueRangeDto";
|
|
import PropertyFormulaValueBadge from "~/components/entities/properties/PropertyFormulaValueBadge";
|
|
import FormulaHelpers from "~/modules/formulas/utils/FormulaHelpers";
|
|
import { SelectOptionsDisplay } from "../shared/SelectOptionsUtils";
|
|
import RowRangeNumberCell from "~/components/entities/rows/cells/RowRangeNumberCell";
|
|
import RowRangeDateCell from "~/components/entities/rows/cells/RowRangeDateCell";
|
|
import PropertyAttributeHelper from "./PropertyAttributeHelper";
|
|
import { translateSelectOptionLabel } from "~/utils/i18n/translateLabel";
|
|
import { renderSanitizedMarkdown } from "~/utils/html-sanitizer";
|
|
// import RelationshipHelper from "./RelationshipHelper";
|
|
|
|
const getCellValue = ({ entity, item, property }: { entity: EntityWithDetails; item: RowWithValues; property: PropertyWithDetails }) => {
|
|
const value = getPropertyValue({ entity, item, property });
|
|
if (property.type === PropertyType.BOOLEAN) {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatBoolean)?.value;
|
|
return <RowBooleanCell value={value as boolean} format={format as BooleanFormatType} />;
|
|
} else if (property.type === PropertyType.SELECT) {
|
|
const display = property.attributes.find((f) => f.name === PropertyAttributeName.SelectOptions)?.value as SelectOptionsDisplay;
|
|
return <PropertyOptionValueBadge entity={entity} property={property.name} value={value as string} display={display} />;
|
|
} else if ([PropertyType.MULTI_SELECT, PropertyType.MULTI_TEXT].includes(property.type)) {
|
|
return <PropertyMultipleValueBadge values={value as RowValueMultiple[]} options={property.options} entityName={entity.name} propertyName={property.name} />;
|
|
} else if ([PropertyType.RANGE_NUMBER].includes(property.type)) {
|
|
const range = value as RowValueRangeDto;
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatNumber)?.value as NumberFormatType;
|
|
return <RowRangeNumberCell value={range} format={format} currencySymbol={undefined} />;
|
|
} else if ([PropertyType.RANGE_DATE].includes(property.type)) {
|
|
const range = value as RowValueRangeDto;
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatDate)?.value as DateFormatType;
|
|
return <RowRangeDateCell value={range} format={format} />;
|
|
} else if ([PropertyType.FORMULA].includes(property.type)) {
|
|
return <PropertyFormulaValueBadge property={property} value={value} />;
|
|
} else if (property.type === PropertyType.NUMBER) {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatNumber)?.value;
|
|
return <RowNumberCell value={value as number} format={format as NumberFormatType} />;
|
|
} else if (property.type === PropertyType.DATE) {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatDate)?.value;
|
|
return <RowDateCell value={value as Date} format={format as DateFormatType} />;
|
|
} else if (property.type === PropertyType.MEDIA) {
|
|
const media = value as MediaDto[];
|
|
return <RowMediaCell media={media} />;
|
|
} else if (property.type === PropertyType.SELECT && value) {
|
|
const display = property.attributes.find((f) => f.name === PropertyAttributeName.SelectOptions)?.value as SelectOptionsDisplay;
|
|
return <PropertyOptionValueBadge entity={entity} property={property.name} value={value as string} display={display} />;
|
|
}
|
|
let formattedValue = getFormattedValue(value, property.type);
|
|
if (PropertyAttributeHelper.getPropertyAttributeValue_Boolean(property, PropertyAttributeName.Password)) {
|
|
formattedValue = "************************";
|
|
} else if (PropertyAttributeHelper.getPropertyAttributeValue_String(property, PropertyAttributeName.Editor) === "wysiwyg") {
|
|
return <div className="truncate" dangerouslySetInnerHTML={{ __html: renderSanitizedMarkdown(formattedValue) }} />;
|
|
}
|
|
return formattedValue;
|
|
};
|
|
|
|
const getValueAsString = ({
|
|
entity,
|
|
item,
|
|
property,
|
|
t,
|
|
}: {
|
|
entity: EntityWithProperties;
|
|
item: { id: string; values: RowValueWithDetails[] };
|
|
property: PropertyWithDetails;
|
|
t?: TFunction;
|
|
}) => {
|
|
const value = getPropertyValue({ entity, item, property });
|
|
if (value === null) {
|
|
return null;
|
|
}
|
|
if (property.type === PropertyType.TEXT) {
|
|
if (property.attributes.find((f) => f.name === PropertyAttributeName.Editor)?.value) {
|
|
return null;
|
|
}
|
|
return value as string;
|
|
} else if (property.type === PropertyType.BOOLEAN) {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatBoolean)?.value;
|
|
return getBooleanAsStringValue({ value: value as boolean, format: format as BooleanFormatType, t });
|
|
} else if (property.type === PropertyType.SELECT) {
|
|
const option = property.options.find((f) => f.value === value);
|
|
if (option) {
|
|
return t ? translateSelectOptionLabel(t, i18next, entity.name, property.name, option) : (option.name ?? option.value);
|
|
}
|
|
return value?.toString();
|
|
} else if ([PropertyType.MULTI_SELECT, PropertyType.MULTI_TEXT].includes(property.type)) {
|
|
return (value as RowValueMultiple[])
|
|
.map((v) => {
|
|
const option = property.options.find((f) => f.value === v.value);
|
|
if (option && t) {
|
|
return translateSelectOptionLabel(t, i18next, entity.name, property.name, option);
|
|
}
|
|
return option?.name ?? v.value;
|
|
})
|
|
.join(", ");
|
|
} else if ([PropertyType.RANGE_NUMBER, PropertyType.RANGE_DATE].includes(property.type)) {
|
|
const range = value as RowValueRangeDto;
|
|
if (property.type === PropertyType.RANGE_NUMBER) {
|
|
const numberFormat = property.attributes.find((f) => f.name === PropertyAttributeName.FormatNumber)?.value as NumberFormatType;
|
|
const numberMin = range?.numberMin ? Number(range?.numberMin) : undefined;
|
|
const numberMax = range?.numberMax ? Number(range?.numberMax) : undefined;
|
|
const min = getNumberAsStringValue({ value: numberMin, format: numberFormat });
|
|
const max = getNumberAsStringValue({ value: numberMax, format: numberFormat });
|
|
return `${min} - ${max}`;
|
|
} else {
|
|
const dateFormat = property.attributes.find((f) => f.name === PropertyAttributeName.FormatDate)?.value as DateFormatType;
|
|
const min = getDateAsString({ value: range?.dateMin, format: dateFormat });
|
|
const max = getDateAsString({ value: range?.dateMax, format: dateFormat });
|
|
return `${min} - ${max}`;
|
|
}
|
|
} else if ([PropertyType.FORMULA].includes(property.type)) {
|
|
if (property.formula?.resultAs === "number") {
|
|
const numberFormat = property.attributes.find((f) => f.name === PropertyAttributeName.FormatNumber)?.value as NumberFormatType;
|
|
return getNumberAsStringValue({ value: value as number, format: numberFormat });
|
|
} else if (property.formula?.resultAs === "boolean") {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatBoolean)?.value;
|
|
return getBooleanAsStringValue({ value: value as boolean, format: format as BooleanFormatType, t });
|
|
} else if (property.formula?.resultAs === "date") {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatDate)?.value;
|
|
return getDateAsString({ value: value as Date, format: format as DateFormatType });
|
|
} else {
|
|
return value?.toString();
|
|
}
|
|
} else if (property.type === PropertyType.NUMBER) {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatNumber)?.value;
|
|
return getNumberAsStringValue({ value: value as number, format: format as NumberFormatType });
|
|
} else if (property.type === PropertyType.DATE) {
|
|
const format = property.attributes.find((f) => f.name === PropertyAttributeName.FormatDate)?.value;
|
|
return getDateAsString({ value: value as Date, format: format as DateFormatType });
|
|
} else if (property.type === PropertyType.MEDIA) {
|
|
const media = value as MediaDto[];
|
|
function getFileName(name: string) {
|
|
let fileName = name;
|
|
if (name.includes("/")) {
|
|
fileName = name.split("/").pop() ?? name;
|
|
}
|
|
if (name.includes("\\")) {
|
|
fileName = name.split("\\").pop() ?? name;
|
|
}
|
|
return fileName;
|
|
}
|
|
return media.map((f) => getFileName(f.title)).join(", ");
|
|
}
|
|
return "";
|
|
};
|
|
|
|
const getPropertyValue = ({
|
|
entity,
|
|
item,
|
|
property,
|
|
propertyName,
|
|
}: {
|
|
entity: Entity & {
|
|
properties: (Property & { formula?: null | { name: string; resultAs: string; calculationTrigger: string } })[];
|
|
};
|
|
item: { values: RowValueWithDetails[] } | null;
|
|
property?: Property & { formula?: null | { name: string; resultAs: string; calculationTrigger: string } };
|
|
propertyName?: string;
|
|
}): string | number | boolean | RowMedia[] | RowValueMultiple[] | RowValueRange | Date | null => {
|
|
if (!property) {
|
|
property = entity.properties.find((f) => f.name === propertyName);
|
|
}
|
|
if (!property) {
|
|
return null;
|
|
}
|
|
const value = item?.values?.find((f) => f?.propertyId === property?.id);
|
|
if (value) {
|
|
if (property.type === PropertyType.FORMULA) {
|
|
const resultAs = FormulaHelpers.getResultAs(property.formula?.resultAs ?? "");
|
|
switch (resultAs) {
|
|
case "number":
|
|
return getDynamicPropertyValue(value, PropertyType.NUMBER);
|
|
case "string":
|
|
return getDynamicPropertyValue(value, PropertyType.TEXT);
|
|
case "date":
|
|
return getDynamicPropertyValue(value, PropertyType.DATE);
|
|
case "boolean":
|
|
return getDynamicPropertyValue(value, PropertyType.BOOLEAN);
|
|
default:
|
|
return null;
|
|
}
|
|
} else {
|
|
return getDynamicPropertyValue(value, property.type);
|
|
}
|
|
}
|
|
try {
|
|
if (!item) {
|
|
return null;
|
|
}
|
|
const object = item[entity?.name as keyof typeof item];
|
|
if (object) {
|
|
// DEPRECATED
|
|
// return object[property.name as keyof typeof object];
|
|
}
|
|
return null;
|
|
} catch {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`Error getting row value.`, { entity: entity?.name, property: property.name });
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const getProperties = (entity: EntityWithProperties, item: RowWithValues | null) => {
|
|
const customProperties: any = {};
|
|
entity.properties
|
|
.filter((f) => !f.isHidden)
|
|
.forEach((property) => {
|
|
if (property.type === PropertyType.MEDIA) {
|
|
const media = (getPropertyValue({ entity, item, property }) ?? []) as RowMedia[];
|
|
const max = property.attributes.find((f) => f.name === PropertyAttributeName.Max);
|
|
if (max?.value && Number(max.value) === 1) {
|
|
if (media?.length > 0) {
|
|
customProperties[property.name] = media[0].publicUrl ?? media[0].file;
|
|
} else {
|
|
customProperties[property.name] = "";
|
|
}
|
|
} else {
|
|
customProperties[property.name] = media.map((item) => item.publicUrl ?? item.file);
|
|
}
|
|
} else {
|
|
const value = getPropertyValue({ entity, item, property });
|
|
customProperties[property.name] = value;
|
|
}
|
|
});
|
|
return customProperties;
|
|
};
|
|
|
|
function getDynamicPropertyValue(item: RowValue & { media: RowMedia[]; multiple: RowValueMultiple[]; range: RowValueRange | null }, type: PropertyType) {
|
|
switch (type) {
|
|
case PropertyType.NUMBER:
|
|
if (item.numberValue === null || isNaN(Number(item.numberValue))) {
|
|
return null;
|
|
}
|
|
return Number(item.numberValue);
|
|
case PropertyType.TEXT:
|
|
case PropertyType.SELECT:
|
|
// case PropertyType.FORMULA:
|
|
return item.textValue;
|
|
case PropertyType.DATE:
|
|
return item.dateValue;
|
|
case PropertyType.BOOLEAN:
|
|
return item.booleanValue;
|
|
// case PropertyType.ROLE:
|
|
// case PropertyType.USER:
|
|
case PropertyType.MEDIA:
|
|
return item.media;
|
|
case PropertyType.MULTI_SELECT:
|
|
case PropertyType.MULTI_TEXT:
|
|
return item.multiple;
|
|
case PropertyType.RANGE_NUMBER:
|
|
case PropertyType.RANGE_DATE:
|
|
return item.range;
|
|
default:
|
|
return getRowValueNotSupported();
|
|
}
|
|
}
|
|
|
|
function getDetailFormattedValue(entity: EntityWithDetails, item: RowWithValues, property: PropertyWithDetails) {
|
|
const value = getPropertyValue({ entity, item, property });
|
|
if (value) {
|
|
return getFormattedValue(value, property.type);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function getFormattedValue(value: any, type: PropertyType, t?: TFunction): string {
|
|
switch (type) {
|
|
case PropertyType.NUMBER:
|
|
if (value === null || isNaN(value)) {
|
|
return "";
|
|
}
|
|
return NumberUtils.decimalFormat(Number(value));
|
|
case PropertyType.TEXT:
|
|
case PropertyType.SELECT:
|
|
// case PropertyType.FORMULA:
|
|
return value;
|
|
case PropertyType.BOOLEAN:
|
|
return getBooleanAsStringValue({ value: Boolean(value), t });
|
|
case PropertyType.DATE:
|
|
if (!value) {
|
|
return "";
|
|
}
|
|
try {
|
|
const date = new Date(value);
|
|
return date.toISOString().split("T")[0];
|
|
} catch {
|
|
return value.toISOString().split("T")[0];
|
|
}
|
|
// case PropertyType.ROLE:
|
|
// case PropertyType.USER:
|
|
// case PropertyType.ID:
|
|
// return value;
|
|
case PropertyType.MEDIA:
|
|
return value?.length ?? "0";
|
|
default:
|
|
return getRowValueNotSupported(t);
|
|
}
|
|
}
|
|
|
|
function getRowValueNotSupported(t?: TFunction): string {
|
|
return (t ?? i18nT)("models.row.values.notSupported");
|
|
}
|
|
|
|
const getRowFolio = (entity: Entity, item: { folio: number }) => {
|
|
return `${entity?.prefix}-${NumberUtils.pad(item?.folio ?? 0, 4)}`.toUpperCase();
|
|
};
|
|
|
|
const getTextDescription = ({
|
|
entity,
|
|
item,
|
|
t,
|
|
defaultsToFolio = true,
|
|
includeFolio,
|
|
}: {
|
|
entity: EntityWithDetails;
|
|
item: { id: string; folio: number; values: RowValueWithDetails[] };
|
|
t?: TFunction;
|
|
defaultsToFolio?: boolean;
|
|
includeFolio?: boolean;
|
|
}): string => {
|
|
const descriptions: string[] = [];
|
|
entity.properties
|
|
.filter((f) => !f.isDefault && f.isDisplay)
|
|
.forEach((property) => {
|
|
const value = getValueAsString({ entity, item, property, t });
|
|
if (value) {
|
|
descriptions.push(value);
|
|
}
|
|
});
|
|
if (descriptions.length > 0) {
|
|
if (includeFolio) {
|
|
descriptions.push(getRowFolio(entity, item));
|
|
}
|
|
return descriptions.join(" • ");
|
|
}
|
|
return defaultsToFolio || includeFolio ? getRowFolio(entity, item) : "";
|
|
};
|
|
|
|
const getFirstProperty = ({ entity, item }: { entity: EntityWithDetails; item: RowWithValues }) => {
|
|
let displayProperties = entity.properties.sort((a, b) => a.order - b.order).filter((f) => f.isDisplay);
|
|
if (displayProperties.length > 0) {
|
|
const property = displayProperties[0];
|
|
const value = getCellValue({ entity, item, property });
|
|
return value;
|
|
} else {
|
|
return getRowFolio(entity, item);
|
|
}
|
|
};
|
|
|
|
const search = (entity: EntityWithDetails, item: RowWithValues, searchInput: string) => {
|
|
if (!searchInput || searchInput.trim() === "") {
|
|
return true;
|
|
}
|
|
const properties = entity.properties.filter((f) => !f.isHidden);
|
|
for (let idx = 0; idx < properties.length; idx++) {
|
|
const property = properties[idx];
|
|
const rowValue = getPropertyValue({ entity, item, property });
|
|
if (rowValue?.toString()?.toUpperCase().trim().includes(searchInput.toUpperCase().trim())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (
|
|
getRowFolio(entity, item)?.toUpperCase().includes(searchInput.toUpperCase()) ||
|
|
item.createdByUser?.email?.toUpperCase().includes(searchInput.toUpperCase()) ||
|
|
(item.createdByUser?.firstName + " " + item.createdByUser?.lastName)?.toUpperCase().includes(searchInput.toUpperCase())
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const updateFieldValueTypeArray = (item: RowValueDto, value: any) => {
|
|
return getValueFromType(item.property.type, value);
|
|
};
|
|
|
|
const getValueFromType = (type: PropertyType, value: any) => {
|
|
switch (type) {
|
|
case PropertyType.NUMBER:
|
|
if (typeof value === "string") {
|
|
return { numberValue: !value ? null : Number(value) };
|
|
}
|
|
return { numberValue: value ?? null };
|
|
case PropertyType.TEXT:
|
|
case PropertyType.SELECT:
|
|
// case PropertyType.FORMULA:
|
|
return { textValue: value };
|
|
case PropertyType.DATE:
|
|
if (value === null || value === undefined || value === "") {
|
|
return { dateValue: undefined };
|
|
} else {
|
|
return { dateValue: new Date(value) };
|
|
}
|
|
case PropertyType.BOOLEAN:
|
|
return { booleanValue: value === "true" || value === true };
|
|
case PropertyType.MEDIA:
|
|
return { media: value };
|
|
// case PropertyType.USER:
|
|
// case PropertyType.ROLE:
|
|
// return { idValue: value };
|
|
case PropertyType.MULTI_SELECT:
|
|
case PropertyType.MULTI_TEXT:
|
|
return { multiple: value };
|
|
case PropertyType.RANGE_NUMBER:
|
|
case PropertyType.RANGE_DATE:
|
|
return { range: value };
|
|
case PropertyType.FORMULA:
|
|
return { textValue: value };
|
|
default:
|
|
return {};
|
|
}
|
|
};
|
|
|
|
export type RowValueCreateDto = {
|
|
name: string;
|
|
value?: string;
|
|
media?: MediaDto[];
|
|
multiple?: RowValueMultipleDto[];
|
|
range?: RowValueRangeDto;
|
|
};
|
|
const getRowPropertiesFromForm = ({
|
|
t,
|
|
entity,
|
|
form,
|
|
values,
|
|
existing,
|
|
options,
|
|
}: {
|
|
t?: TFunction;
|
|
entity: EntityWithDetails;
|
|
form?: FormData;
|
|
values?: RowValueCreateDto[];
|
|
existing?: RowWithValues;
|
|
options?: {
|
|
skipValidation?: boolean;
|
|
};
|
|
}): {
|
|
dynamicProperties: RowValueDto[];
|
|
properties: any;
|
|
parentRows?: { relationshipId: string; parentId: string }[];
|
|
childRows?: { relationshipId: string; childId: string }[];
|
|
} => {
|
|
const dynamicProperties: RowValueDto[] = [];
|
|
|
|
entity.properties
|
|
.filter((f) => !f.isHidden && !f.isDefault)
|
|
.forEach((property) => {
|
|
let formValue: FormDataEntryValue | null = null;
|
|
let media: MediaDto[] = [];
|
|
let multiple: RowValueMultipleDto[] = [];
|
|
let range: RowValueRangeDto | null = null;
|
|
let name = property.name;
|
|
const propertyValue = values?.find((f) => f.name === name);
|
|
if ([PropertyType.MEDIA, PropertyType.MULTI_SELECT, PropertyType.MULTI_TEXT].includes(property.type)) {
|
|
name += "[]";
|
|
}
|
|
if ([PropertyType.RANGE_NUMBER, PropertyType.RANGE_DATE].includes(property.type)) {
|
|
if (propertyValue?.range) {
|
|
range = propertyValue.range;
|
|
} else {
|
|
const min = getFormDataEntryValue({ name: name + "-min", form, values });
|
|
const max = getFormDataEntryValue({ name: name + "-max", form, values });
|
|
if (PropertyType.RANGE_NUMBER === property.type) {
|
|
range = {
|
|
numberMin: Number(min ?? 0),
|
|
numberMax: Number(max ?? 0),
|
|
dateMin: null,
|
|
dateMax: null,
|
|
};
|
|
} else if (PropertyType.RANGE_DATE === property.type) {
|
|
range = {
|
|
numberMin: null,
|
|
numberMax: null,
|
|
dateMin: min ? new Date(min?.toString()) : null,
|
|
dateMax: max ? new Date(max?.toString()) : null,
|
|
};
|
|
}
|
|
}
|
|
} else if (property.type === PropertyType.MEDIA) {
|
|
if (propertyValue?.media) {
|
|
media = propertyValue.media;
|
|
} else {
|
|
media = getFormDataEntryValues({ name, form, values }).map((f: FormDataEntryValue) => {
|
|
return JSON.parse(f.toString());
|
|
});
|
|
}
|
|
if (!options?.skipValidation) {
|
|
validatePropertyValue_Media({ t, property, media });
|
|
}
|
|
} else if ([PropertyType.MULTI_SELECT, PropertyType.MULTI_TEXT].includes(property.type)) {
|
|
if (propertyValue?.multiple) {
|
|
multiple = propertyValue.multiple;
|
|
} else {
|
|
multiple = getFormDataEntryValues({ name, form, values }).map((f: FormDataEntryValue) => {
|
|
return JSON.parse(f.toString());
|
|
});
|
|
}
|
|
if (!options?.skipValidation) {
|
|
validatePropertyValue_Multiple({ t, property, multiple });
|
|
}
|
|
} else {
|
|
formValue = getFormDataEntryValue({ name, form, values });
|
|
if (!options?.skipValidation) {
|
|
if (property.isRequired && !property.isReadOnly && (formValue === null || formValue === undefined || !formValue)) {
|
|
if (t) {
|
|
throw Error(`${t(property.title)}: required`);
|
|
} else {
|
|
throw Error(`${property.name}: required`);
|
|
}
|
|
}
|
|
validatePropertyValue({ t, property, value: formValue });
|
|
}
|
|
}
|
|
const value = getValueFromType(property.type, formValue);
|
|
value.media = media;
|
|
value.multiple = multiple;
|
|
value.range = range;
|
|
|
|
let hasFormValue = formValue !== null && formValue !== undefined;
|
|
if (property.type === PropertyType.MEDIA) {
|
|
hasFormValue = media.length > 0;
|
|
} else if ([PropertyType.MULTI_SELECT, PropertyType.MULTI_TEXT].includes(property.type)) {
|
|
hasFormValue = multiple.length > 0;
|
|
} else if ([PropertyType.RANGE_NUMBER, PropertyType.RANGE_DATE].includes(property.type)) {
|
|
hasFormValue = range !== null;
|
|
}
|
|
if (!hasFormValue) {
|
|
if (!property.isRequired) {
|
|
return;
|
|
} else if (property.isReadOnly) {
|
|
return;
|
|
} else if (!property.canUpdate && existing) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const existingValue = existing?.values?.find((f) => f.propertyId === property.id);
|
|
const rowValue = {
|
|
id: existingValue?.id ?? null,
|
|
propertyId: property.id,
|
|
property: property,
|
|
...value,
|
|
};
|
|
dynamicProperties.push(rowValue);
|
|
});
|
|
|
|
let parentRows: { relationshipId: string; parentId: string }[] | undefined = undefined;
|
|
let childRows: { relationshipId: string; childId: string }[] | undefined = undefined;
|
|
|
|
entity.parentEntities.forEach((relationship) => {
|
|
let name = "parents[" + relationship.parent.name + "]";
|
|
if (form?.has(name)) {
|
|
parentRows = parentRows ?? [];
|
|
}
|
|
const rowIds = getFormDataEntryValues({ name, form, values }).map((f) => f.toString());
|
|
const parentRowIds = rowIds.map((parentId) => {
|
|
return { relationshipId: relationship.id, parentId };
|
|
});
|
|
if (relationship.required && parentRowIds.length === 0) {
|
|
throw Error(`${t ? t(relationship.parent.title) : relationship.parent.name}: required`);
|
|
}
|
|
parentRows = parentRows?.concat(parentRowIds);
|
|
// if (relationship.required && parentRowIds.length === 0) {
|
|
// throw Error(`${t ? t(RelationshipHelper.getTitle({ fromEntityId: entity.id, relationship })) : relationship.parent.name}: required`);
|
|
// }
|
|
});
|
|
|
|
entity.childEntities.forEach((relationship) => {
|
|
let name = "children[" + relationship.child.name + "]";
|
|
if (form?.has(name)) {
|
|
childRows = childRows ?? [];
|
|
}
|
|
const rowIds = getFormDataEntryValues({ name, form, values }).map((f) => f.toString());
|
|
const childRowIds = rowIds.map((childId) => {
|
|
return { relationshipId: relationship.id, childId };
|
|
});
|
|
childRows = childRows?.concat(childRowIds);
|
|
});
|
|
|
|
return {
|
|
dynamicProperties,
|
|
properties: {},
|
|
parentRows,
|
|
childRows,
|
|
};
|
|
};
|
|
|
|
function getFormDataEntryValue({
|
|
name,
|
|
form,
|
|
values,
|
|
}: {
|
|
name: string;
|
|
form?: FormData;
|
|
values?: { name: string; value?: string }[];
|
|
}): FormDataEntryValue | null {
|
|
if (form) {
|
|
return form.get(name);
|
|
} else {
|
|
return values?.find((f) => f.name === name)?.value ?? null;
|
|
}
|
|
}
|
|
|
|
function getFormDataEntryValues({ name, form, values }: { name: string; form?: FormData; values?: { name: string; value?: string }[] }): FormDataEntryValue[] {
|
|
let entries: FormDataEntryValue[] = [];
|
|
if (form) {
|
|
entries = form.getAll(name);
|
|
} else {
|
|
entries = values?.filter((f) => f.name === name).map((f) => f.value ?? "") ?? [];
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
const getPropertyDefaultValue = (property: Property): string | number | boolean | RowMedia[] | RowValueMultiple[] | RowValueRange | Date | null => {
|
|
switch (property.type) {
|
|
case PropertyType.NUMBER:
|
|
return 0;
|
|
case PropertyType.TEXT:
|
|
case PropertyType.SELECT:
|
|
return "";
|
|
case PropertyType.DATE:
|
|
return null;
|
|
case PropertyType.BOOLEAN:
|
|
return false;
|
|
case PropertyType.MEDIA:
|
|
return [];
|
|
case PropertyType.MULTI_SELECT:
|
|
case PropertyType.MULTI_TEXT:
|
|
return [];
|
|
case PropertyType.RANGE_NUMBER:
|
|
case PropertyType.RANGE_DATE:
|
|
return {
|
|
rowValueId: "",
|
|
numberMin: null,
|
|
numberMax: null,
|
|
dateMin: null,
|
|
dateMax: null,
|
|
};
|
|
case PropertyType.FORMULA:
|
|
return null;
|
|
default:
|
|
return getRowValueNotSupported();
|
|
}
|
|
};
|
|
|
|
const getFakePropertyValue = ({ property, idx, t }: { property: PropertyWithDetails; idx: number; t: TFunction }): RowValueWithDetails => {
|
|
let sampleText = `${t(property.title)} ${idx}`;
|
|
const rowValue: RowValueWithDetails = {
|
|
id: "1",
|
|
createdAt: new Date(),
|
|
updatedAt: null,
|
|
rowId: "1",
|
|
propertyId: property.id,
|
|
textValue: null,
|
|
numberValue: null,
|
|
dateValue: null,
|
|
booleanValue: null,
|
|
media: [],
|
|
multiple: [],
|
|
range: null,
|
|
};
|
|
switch (property.type) {
|
|
case PropertyType.FORMULA: {
|
|
const resultAs = FormulaHelpers.getResultAs(property.formula?.resultAs ?? "");
|
|
switch (resultAs) {
|
|
case "number":
|
|
rowValue.numberValue = new Decimal(123);
|
|
break;
|
|
case "string":
|
|
rowValue.textValue = sampleText;
|
|
break;
|
|
case "date":
|
|
rowValue.dateValue = new Date();
|
|
break;
|
|
case "boolean":
|
|
rowValue.booleanValue = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
case PropertyType.NUMBER:
|
|
rowValue.numberValue = new Decimal(123 * idx);
|
|
break;
|
|
case PropertyType.TEXT:
|
|
if (property.subtype === "email") {
|
|
rowValue.textValue = `john.doe.${idx}@company.com`;
|
|
} else if (property.subtype === "url") {
|
|
rowValue.textValue = "https://www.example.com";
|
|
} else if (property.subtype === "phone") {
|
|
rowValue.textValue = "+1 123 456 7890";
|
|
} else {
|
|
rowValue.textValue = sampleText;
|
|
}
|
|
break;
|
|
case PropertyType.SELECT:
|
|
if (property.options.length > 0) {
|
|
rowValue.textValue = property.options[0].value;
|
|
} else {
|
|
rowValue.textValue = sampleText;
|
|
}
|
|
break;
|
|
case PropertyType.DATE:
|
|
rowValue.dateValue = new Date();
|
|
break;
|
|
case PropertyType.BOOLEAN:
|
|
rowValue.booleanValue = true;
|
|
break;
|
|
case PropertyType.MEDIA:
|
|
const media: RowMedia[] = [
|
|
{
|
|
id: "1",
|
|
rowValueId: "",
|
|
file: "",
|
|
type: "image/png",
|
|
name: "Image 1.png",
|
|
title: "Image 1",
|
|
publicUrl: "https://placehold.co/150",
|
|
storageBucket: null,
|
|
storageProvider: null,
|
|
},
|
|
];
|
|
rowValue.media = media;
|
|
break;
|
|
case PropertyType.MULTI_TEXT: {
|
|
let multiple: RowValueMultiple[] = [
|
|
{ id: "1", rowValueId: "", order: 1, value: sampleText },
|
|
{ id: "2", rowValueId: "", order: 2, value: sampleText },
|
|
];
|
|
rowValue.multiple = multiple;
|
|
break;
|
|
}
|
|
case PropertyType.MULTI_SELECT: {
|
|
let multiple: RowValueMultiple[] = [
|
|
{ id: "1", rowValueId: "", order: 1, value: sampleText },
|
|
{ id: "2", rowValueId: "", order: 2, value: sampleText },
|
|
];
|
|
if (property.options.length > 0) {
|
|
multiple = property.options.map((option, index) => {
|
|
return { id: `${index}`, rowValueId: "", order: index, value: option.value };
|
|
});
|
|
}
|
|
rowValue.multiple = multiple;
|
|
break;
|
|
}
|
|
case PropertyType.RANGE_NUMBER:
|
|
const rangeNumber = {
|
|
rowValueId: "",
|
|
numberMin: new Decimal(100),
|
|
numberMax: new Decimal(200),
|
|
dateMin: null,
|
|
dateMax: null,
|
|
};
|
|
rowValue.range = rangeNumber;
|
|
break;
|
|
case PropertyType.RANGE_DATE:
|
|
const rangeDate = {
|
|
rowValueId: "",
|
|
numberMin: null,
|
|
numberMax: null,
|
|
dateMin: new Date(),
|
|
dateMax: new Date(),
|
|
};
|
|
rowValue.range = rangeDate;
|
|
break;
|
|
}
|
|
return rowValue;
|
|
};
|
|
|
|
const getDiff = ({ entity, before, after }: { entity: EntityWithProperties; before: RowWithValues; after: RowWithValues }) => {
|
|
const propertiesToCheck = entity.properties.filter((f) => !f.isDefault);
|
|
const diff: { [key: string]: { before: any; after: any } } = {};
|
|
propertiesToCheck.forEach((property) => {
|
|
const beforeValue = getFormattedValue(getPropertyValue({ entity, item: before, property }), property.type);
|
|
const afterValue = getFormattedValue(getPropertyValue({ entity, item: after, property }), property.type);
|
|
if (beforeValue !== afterValue) {
|
|
diff[property.name] = { before: beforeValue, after: afterValue };
|
|
}
|
|
});
|
|
return diff;
|
|
};
|
|
|
|
export default {
|
|
getCellValue,
|
|
getPropertyValue,
|
|
getDetailFormattedValue,
|
|
getFormattedValue,
|
|
getRowFolio,
|
|
search,
|
|
updateFieldValueTypeArray,
|
|
getValueFromType,
|
|
getProperties,
|
|
getDynamicPropertyValue,
|
|
getRowPropertiesFromForm,
|
|
getFirstProperty,
|
|
getTextDescription,
|
|
getPropertyDefaultValue,
|
|
getFakePropertyValue,
|
|
getValueAsString,
|
|
getDiff,
|
|
};
|