Skip to content

Commit

Permalink
Edit validation rules (finos#1494)
Browse files Browse the repository at this point in the history
* define edit validation rules and EditForm

* apply edit validation rules and display in ui

* move edit dataSource management into edit form

* remove cypress test thats no longert applicable

* add edit conformetion prompt
  • Loading branch information
heswell authored Sep 19, 2024
1 parent 7cd7695 commit 3d020cc
Show file tree
Hide file tree
Showing 58 changed files with 1,503 additions and 867 deletions.
48 changes: 48 additions & 0 deletions vuu-ui/packages/vuu-data-react/src/data-editing/EditForm.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.EditForm {
display: flex;
flex-direction: column;
gap: var(--salt-spacing-400);
height: 100%;
padding: var(--salt-spacing-200) var(--salt-spacing-200)
var(--salt-spacing-200) var(--salt-spacing-400);
width: 100%;
}

.EditForm-form-fields {
display: flex;
flex-direction: column;
gap: var(--salt-spacing-400);
}

.EditForm-buttons {
align-items: center;
display: flex;
gap: var(--salt-spacing-200);
justify-content: flex-end;
}

.EditForm-field {
display: flex;

.saltFormField {
flex: 1 1 auto;
}

.EditForm-edit-indicator {
flex: 0 0 14px;
position: relative;
}

&[data-edited="true"] {
.EditForm-edit-indicator:after {
background-color: var(--salt-content-secondary-foreground);
border-radius: 5px;
content: "";
height: 10px;
position: absolute;
top: 50%;
right: -3px;
width: 10px;
}
}
}
100 changes: 100 additions & 0 deletions vuu-ui/packages/vuu-data-react/src/data-editing/EditForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { getDataItemEditControl } from "@finos/vuu-data-react";
import { DataSource, DataValueDescriptor } from "@finos/vuu-data-types";
import { Button, FormField, FormFieldLabel } from "@salt-ds/core";
import { useComponentCssInjection } from "@salt-ds/styles";
import { useWindow } from "@salt-ds/window";
import cx from "clsx";
import { HTMLAttributes } from "react";
import { registerRules } from "./edit-validation-rules";
import { useEditForm } from "./useEditForm";

import editFormCss from "./EditForm.css";

const classBase = "EditForm";

registerRules();

export interface EditFormProps extends HTMLAttributes<HTMLDivElement> {
dataSource?: DataSource;
formFieldDescriptors: DataValueDescriptor[];
onSubmit?: () => void;
}

export const EditForm = ({
className,
dataSource,
formFieldDescriptors,
onSubmit: onSubmitProp,
...htmlAttributes
}: EditFormProps) => {
const targetWindow = useWindow();
useComponentCssInjection({
testId: "vuu-edit-form",
css: editFormCss,
window: targetWindow,
});

const {
editedFields,
editEntity,
errorMessages,
formFieldsContainerRef,
isClean,
ok,
onCancel,
onChange,
onCommit,
onFocus,
onSubmit,
} = useEditForm({
dataSource,
formFieldDescriptors,
onSubmit: onSubmitProp,
});

return (
<div
{...htmlAttributes}
className={cx(classBase, className)}
onFocus={onFocus}
>
<div className={`${classBase}-form-fields`} ref={formFieldsContainerRef}>
{formFieldDescriptors.map((dataDescriptor) => {
const { name, label = name } = dataDescriptor;
const errorMessage = errorMessages[name];
const isEdited = !isClean && editedFields.includes(name);

return (
<div
className={`${classBase}-field`}
key={name}
data-edited={isEdited}
>
<FormField data-field={name}>
<FormFieldLabel>{label}</FormFieldLabel>
{getDataItemEditControl({
InputProps: {
onChange,
value: editEntity?.[name]?.toString() ?? "",
},
dataDescriptor,
errorMessage,
onCommit,
})}
</FormField>
<div className={`${classBase}-edit-indicator`} />
</div>
);
})}
</div>
<div className={`${classBase}-buttons`}>
<Button disabled={isClean} onClick={onCancel}>
Cancel
</Button>
<Button onClick={onSubmit} disabled={!ok || isClean}>
Save
</Button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.vuuUnsavedChanges-table {
border-collapse: collapse;
width: 100%;
}

.vuuUnsavedChanges-row {
box-sizing: content-box;
border-bottom: solid 1px var(--salt-separable-secondary-borderColor);
height: 32px;

td {
padding: 0 var(--salt-spacing-200);
}
}

.vuuUnsavedChanges-fieldName {
text-transform: capitalize;
}

.vuuUnsavedChanges-new {
font-weight: bold;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Entity } from "@finos/vuu-utils";
import { useComponentCssInjection } from "@salt-ds/styles";
import { useWindow } from "@salt-ds/window";
import { buildFormEditState } from "./form-edit-state";

import unsavedChangesCss from "./UnsavedChangesReport.css";

const classBase = "vuuUnsavedChanges";

export interface UnsavedChangesReportProps<T extends Entity = Entity> {
entity: T;
editedEntity: T;
}

export const UnsavedChangesReport = ({
entity,
editedEntity,
}: UnsavedChangesReportProps) => {
const targetWindow = useWindow();
useComponentCssInjection({
testId: "vuu-unsaved-changes-report",
css: unsavedChangesCss,
window: targetWindow,
});

const { editedFields } = buildFormEditState(entity, editedEntity);

return (
<div className={classBase}>
<table className={`${classBase}-table`}>
<tbody>
{editedFields.map((fieldName, i) => (
<tr className={`${classBase}-row`} key={i}>
<td className={`${classBase}-fieldName`}>{fieldName}</td>
<td className={`${classBase}-old`}>{entity[fieldName]}</td>
<td className={`${classBase}-new`}>{editedEntity[fieldName]}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type {
DataValueDescriptor,
DataValueValidationChecker,
DataValueValidationResult,
EditRuleValidationSuccessResult,
EditValidationRule,
} from "@finos/vuu-data-types";
import type { VuuRowDataItemType } from "@finos/vuu-protocol-types";
import { getEditRuleValidator, isTypeDescriptor } from "@finos/vuu-utils";

export const OK: EditRuleValidationSuccessResult = { ok: true };

const NO_VALIDATION_RULES: EditValidationRule[] = [] as const;

export function getEditValidationRules(
descriptor: DataValueDescriptor,
apply: "change" | "commit",
) {
if (isTypeDescriptor(descriptor.type)) {
return (
descriptor.type.rules?.filter(({ apply: a = "commit" }) => a === apply) ??
NO_VALIDATION_RULES
);
}

return NO_VALIDATION_RULES;
}

export const buildValidationChecker =
(rules: EditValidationRule[]): DataValueValidationChecker =>
(value?: VuuRowDataItemType) =>
applyRules(rules, value);

function applyRules(rules: EditValidationRule[], value?: VuuRowDataItemType) {
const result: { ok: boolean; messages?: string[] } = { ok: true };
for (const rule of rules) {
const applyRuleToValue = getEditRuleValidator(rule.name);
if (applyRuleToValue) {
const res = applyRuleToValue(rule, value);
if (!res.ok) {
result.ok = false;
(result.messages ?? (result.messages = [])).push(res.message);
}
} else {
throw Error(
`editable-utils applyRules, no validator registered for rule '${rule.name}'`,
);
}
}
return result as DataValueValidationResult;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { registerComponent } from "@finos/vuu-utils";
import { VuuRowDataItemType } from "@finos/vuu-protocol-types";
import { EditRuleValidator } from "@finos/vuu-data-types";
import { OK } from "./edit-rule-validation-checker";

const isString = (value?: VuuRowDataItemType): value is string =>
typeof value === "string";

const NUMERIC = /^(?:[0-9]|\.)+$/;

const CharValidatorNumeric: EditRuleValidator = (rule, value) => {
if (isString(value)) {
if (value.trim() === "") {
return OK;
} else if (value.match(NUMERIC)) {
return OK;
}
}
return { ok: false, message: "only numeric characters are permitted" };
};

const ValueValidatorInteger: EditRuleValidator = (rule, value) => {
if (isString(value)) {
if (value.trim() === "") {
return OK;
} else {
if (!value.match(NUMERIC)) {
return {
ok: false,
message: "value must be an integer, invalid character",
};
}
if (parseFloat(value) === parseInt(value)) {
return OK;
}
}
}
return { ok: false, message: "must be an integer value" };
};

export const registerRules = () => {
registerComponent(
"char-numeric",
CharValidatorNumeric,
"data-edit-validator",
{},
);
registerComponent(
"value-integer",
ValueValidatorInteger,
"data-edit-validator",
{},
);
};
Loading

0 comments on commit 3d020cc

Please sign in to comment.