Added a standard component for the validationErrorIcon.
Fixed a rampant re rendering issue.
This commit is contained in:
parent
849d0177ec
commit
f530fc7efa
@ -1,4 +1,4 @@
|
|||||||
@import './global.scss';
|
@import "./global.scss";
|
||||||
|
|
||||||
@mixin table {
|
@mixin table {
|
||||||
background: $white;
|
background: $white;
|
||||||
@ -34,3 +34,6 @@ thead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.align-top {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
|
import ValidationErrorIcon from "../validationErrorIcon";
|
||||||
|
|
||||||
interface TabHeaderProps {
|
interface TabHeaderProps {
|
||||||
id: string;
|
id: string;
|
||||||
@ -26,11 +25,7 @@ export default function TabHeader({
|
|||||||
return (
|
return (
|
||||||
<li className={className} onClick={handleClick}>
|
<li className={className} onClick={handleClick}>
|
||||||
{label}
|
{label}
|
||||||
{hasError && (
|
<ValidationErrorIcon visible={hasError} />
|
||||||
<span className="error-icon">
|
|
||||||
<FontAwesomeIcon icon={faExclamationCircle} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/components/validationErrorIcon.tsx
Normal file
21
src/components/validationErrorIcon.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface ValidationErrorIconProps {
|
||||||
|
visible: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ValidationErrorIcon: React.FC<ValidationErrorIconProps> = ({
|
||||||
|
visible,
|
||||||
|
}) => {
|
||||||
|
if (!visible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="error-icon">
|
||||||
|
<FontAwesomeIcon icon={faExclamationCircle} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ValidationErrorIcon;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Namespaces } from "../../../i18n/i18n";
|
import { Namespaces } from "../../../i18n/i18n";
|
||||||
import HorizontalTabs from "../../../components/common/HorizionalTabs";
|
import HorizontalTabs from "../../../components/common/HorizionalTabs";
|
||||||
@ -131,20 +131,12 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
|
|||||||
|
|
||||||
//const [tasksValid, setTasksValid] = useState(true);
|
//const [tasksValid, setTasksValid] = useState(true);
|
||||||
|
|
||||||
const handleTaskValidate = (taskId: string, isValid: boolean) => {
|
const handleTaskValidate = useCallback((taskId: string, isValid: boolean) => {
|
||||||
setTaskValidation((prev) => {
|
setTaskValidation((prev) => {
|
||||||
const updated = { ...prev, [taskId]: isValid };
|
const updated = { ...prev, [taskId]: isValid };
|
||||||
|
|
||||||
// Compute overall validity
|
|
||||||
//const allValid = Object.values(updated).every((v) => v === true);
|
|
||||||
|
|
||||||
// Bubble up to parent
|
|
||||||
//setTasksValid(allValid);
|
|
||||||
//setTaskValidation(updated);
|
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const { loaded, redirect, errors: formErrors, data } = form.state;
|
const { loaded, redirect, errors: formErrors, data } = form.state;
|
||||||
if (redirect) return <Navigate to={redirect} />;
|
if (redirect) return <Navigate to={redirect} />;
|
||||||
|
|||||||
@ -8,9 +8,12 @@ import {
|
|||||||
defaultsContext,
|
defaultsContext,
|
||||||
} from "../useCapabilityDefaults";
|
} from "../useCapabilityDefaults";
|
||||||
import Button, { ButtonType } from "../../../../../components/common/Button";
|
import Button, { ButtonType } from "../../../../../components/common/Button";
|
||||||
|
import ValidationErrorIcon from "../../../../../components/validationErrorIcon";
|
||||||
|
import ErrorBlock from "../../../../../components/common/ErrorBlock";
|
||||||
|
|
||||||
// TODO: Replace with your real RolePicker and RaciPicker
|
// TODO: Replace with your real RolePicker and RaciPicker
|
||||||
const RolePicker = (props: any) => (
|
const RolePicker = (props: any) => (
|
||||||
|
<div className="form-group">
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -21,9 +24,11 @@ const RolePicker = (props: any) => (
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const RaciPicker = (props: any) => (
|
const RaciPicker = (props: any) => (
|
||||||
|
<div className="form-group">
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<select
|
<select
|
||||||
name={props.name}
|
name={props.name}
|
||||||
@ -36,6 +41,7 @@ const RaciPicker = (props: any) => (
|
|||||||
<option value="Informed">Informed</option>
|
<option value="Informed">Informed</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@ -97,6 +103,7 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
<th>Contact</th>
|
<th>Contact</th>
|
||||||
<th>RACI</th>
|
<th>RACI</th>
|
||||||
@ -109,7 +116,16 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{assignees.map((assignee, index) => (
|
{assignees.map((assignee, index) => (
|
||||||
<tr key={index}>
|
<tr key={index} className="align-top">
|
||||||
|
<td className="form-group">
|
||||||
|
<ValidationErrorIcon
|
||||||
|
visible={
|
||||||
|
!!fieldErrors?.[`${guid}.assignees[${index}].role`] ||
|
||||||
|
!!fieldErrors?.[`${guid}.assignees[${index}].contact`] ||
|
||||||
|
!!fieldErrors?.[`${guid}.assignees[${index}].raci`]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<RolePicker
|
<RolePicker
|
||||||
name="role"
|
name="role"
|
||||||
@ -144,7 +160,7 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td className="form-group">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => removeAssignee(index)}
|
onClick={() => removeAssignee(index)}
|
||||||
buttonType={ButtonType.secondary}
|
buttonType={ButtonType.secondary}
|
||||||
@ -156,6 +172,15 @@ export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<ErrorBlock
|
||||||
|
error={Object.values(fieldErrors ?? {})
|
||||||
|
.filter((_, i) =>
|
||||||
|
Object.keys(fieldErrors ?? {}).some(
|
||||||
|
(key) => key === `${guid}.assignees`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.join("; ")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { sortTasksTopologically } from "./workflowGraphUtils";
|
import { sortTasksTopologically } from "./workflowGraphUtils";
|
||||||
import { useCapabilityDefaults, validateTask } from "./useCapabilityDefaults";
|
import { useCapabilityDefaults, validateTask } from "./useCapabilityDefaults";
|
||||||
|
import ValidationErrorIcon from "../../../../components/validationErrorIcon";
|
||||||
|
|
||||||
interface TaskListProps {
|
interface TaskListProps {
|
||||||
tasks: TaskDefinition[];
|
tasks: TaskDefinition[];
|
||||||
@ -70,11 +71,11 @@ const TaskList: React.FC<TaskListProps> = ({
|
|||||||
<>
|
<>
|
||||||
{x.config.name as string}
|
{x.config.name as string}
|
||||||
|
|
||||||
{validTasksList[x.config.guid as string] === false && (
|
{
|
||||||
<span className="error-icon">
|
<ValidationErrorIcon
|
||||||
<FontAwesomeIcon icon={faExclamationCircle} />
|
visible={validTasksList[x.config.guid as string] === false}
|
||||||
</span>
|
/>
|
||||||
)}
|
}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
onSelect={(item) => onSelectTask(item)}
|
onSelect={(item) => onSelectTask(item)}
|
||||||
|
|||||||
@ -27,26 +27,23 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
|
|||||||
|
|
||||||
const runValidation = useCallback(
|
const runValidation = useCallback(
|
||||||
(
|
(
|
||||||
bubbleUp = true,
|
|
||||||
taskToValidate: TaskDefinition,
|
taskToValidate: TaskDefinition,
|
||||||
tasksList: TaskDefinition[],
|
tasksList: TaskDefinition[],
|
||||||
tasksMetadataList: TaskMetadata[],
|
tasksMetadataList: TaskMetadata[],
|
||||||
) => {
|
) => {
|
||||||
const errors = validateTask(taskToValidate, tasksList, tasksMetadataList);
|
const errors = validateTask(taskToValidate, tasksList, tasksMetadataList);
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
|
|
||||||
if (bubbleUp) {
|
|
||||||
onValidate(
|
onValidate(
|
||||||
taskToValidate.config.guid as string,
|
taskToValidate.config.guid as string,
|
||||||
Object.keys(errors).length === 0,
|
Object.keys(errors).length === 0,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
[onValidate],
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
runValidation(true, task, tasks, tasksMetadata);
|
runValidation(task, tasks, tasksMetadata);
|
||||||
}, [runValidation, task, tasks, tasksMetadata]);
|
}, [task.config.guid, runValidation]);
|
||||||
|
|
||||||
const handleTaskChange = (updatedTask: TaskDefinition) => {
|
const handleTaskChange = (updatedTask: TaskDefinition) => {
|
||||||
// Update the task list
|
// Update the task list
|
||||||
@ -54,16 +51,7 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
|
|||||||
t.config.guid === updatedTask.config.guid ? updatedTask : t,
|
t.config.guid === updatedTask.config.guid ? updatedTask : t,
|
||||||
);
|
);
|
||||||
|
|
||||||
runValidation(true, updatedTask, updatedTasks, tasksMetadata);
|
runValidation(updatedTask, updatedTasks, tasksMetadata);
|
||||||
// // Run validation
|
|
||||||
// const errors = validateTask(updatedTask, updatedTasks, tasksMetadata);
|
|
||||||
// setFieldErrors(errors);
|
|
||||||
|
|
||||||
// // Bubble validity up
|
|
||||||
// onValidate(
|
|
||||||
// updatedTask.config.guid as string,
|
|
||||||
// Object.keys(errors).length === 0,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Bubble updated task up
|
// Bubble updated task up
|
||||||
onChange(updatedTask);
|
onChange(updatedTask);
|
||||||
|
|||||||
@ -66,7 +66,7 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="two-column-grid">
|
<div className="two-column-grid no-scroll">
|
||||||
<div className="fit-content-width`">
|
<div className="fit-content-width`">
|
||||||
<TaskList
|
<TaskList
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user