Skip to content

Commit

Permalink
display validation error on graph node/edge form #109
Browse files Browse the repository at this point in the history
  • Loading branch information
sim51 committed Jan 29, 2024
1 parent 327b130 commit 68d6a04
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 86 deletions.
4 changes: 4 additions & 0 deletions src/locales/dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"title": "Page Not Found",
"subtitle": "The page you were looking for doesn't exist.",
"paragraph": "You may have mistyped the address or the page may have moved."
},
"form": {
"required": "Field is required",
"unique": "Value must be unique, and a data with the same value already exist"
}
},
"user": {
Expand Down
6 changes: 5 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
"title": "Page Not Found",
"subtitle": "The page you were looking for doesn't exist.",
"paragraph": "You may have mistyped the address or the page may have moved."
}
},
"form": {
"required": "Field is required",
"unique": "Value must be unique, and a data with the same value already exist"
}
},
"user": {
"avatar_alt": "{{name}}'s avatar"
Expand Down
4 changes: 4 additions & 0 deletions src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"title": "Page non trouvée",
"subtitle": "La page que vous recherchez n'existe pas.",
"paragraph": "Vous avez peut-être mal saisi l'adresse ou la page a été déplacée."
},
"form": {
"required": "Champs requis",
"unique": "Une autre entrée existe déjà avec cette valeur"
}
},
"user": {
Expand Down
11 changes: 11 additions & 0 deletions src/styles/_forms.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.form-control.react-select.is-invalid {
border: none;
padding-left:0;
padding-top:0;
padding-bottom: 0;

> div {
border-color: var(--bs-form-invalid-border-color);
box-shadow: none;
}
}
1 change: 1 addition & 0 deletions src/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
@import "loader";
@import "user";
@import "filters";
@import "forms";
@import "slider";
@import "highlightjs";
@import "graph-caption";
Expand Down
83 changes: 53 additions & 30 deletions src/views/graphPage/modals/edition/UpdateEdgeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BsFillTrashFill } from "react-icons/bs";
import { FaTimes } from "react-icons/fa";
import Select from "react-select";
import { StateManagerProps } from "react-select/dist/declarations/src/useStateManager";
import cx from "classnames";

import { Modal } from "../../../../components/modals";
import { useGraphDataset, useGraphDatasetActions, useSelectionActions } from "../../../../core/context/dataContexts";
Expand Down Expand Up @@ -68,7 +69,15 @@ const UpdateEdgeModal: FC<ModalProps<{ edgeId?: string }>> = ({ cancel, submit,
})),
};
}, [edgeData, edgeId, edgeRenderingData, fullGraph, isNew, nodeRenderingData]);
const { register, handleSubmit, control, setValue, getValues, watch } = useForm<UpdatedEdgeState>({
const {
register,
handleSubmit,
control,
setValue,
getValues,
watch,
formState: { errors },
} = useForm<UpdatedEdgeState>({
defaultValues,
});
const attributes = watch("attributes");
Expand Down Expand Up @@ -139,10 +148,15 @@ const UpdateEdgeModal: FC<ModalProps<{ edgeId?: string }>> = ({ cancel, submit,
<input
type="text"
id="updateEdge-id"
className="form-control"
className={cx("form-control", errors.id && "is-invalid")}
disabled={!isNew}
{...register("id", { required: "true", validate: (value) => !!value && (!isNew || !edgeData[value]) })}
/>
{errors.id && (
<div className="invalid-feedback">
{t(`error.form.${errors.id.type === "validate" ? "unique" : errors.id.type}`)}
</div>
)}
</div>
<div className="col-md-6">
<label htmlFor="updateEdge-label" className="form-label">
Expand Down Expand Up @@ -176,12 +190,14 @@ const UpdateEdgeModal: FC<ModalProps<{ edgeId?: string }>> = ({ cancel, submit,
<Select
{...field}
{...nodeSelectProps}
className={cx(errors.source && "form-control react-select is-invalid")}
isDisabled={!isNew}
onChange={(newValue) => onChange(newValue as NodeOption)}
id="updateEdge-source"
/>
)}
/>
{errors.source && <div className="invalid-feedback">{t(`error.form.${errors.source.type}`)}</div>}
</div>
<div className="col-md-6">
<label htmlFor="updateEdge-target" className="form-label">
Expand All @@ -198,12 +214,14 @@ const UpdateEdgeModal: FC<ModalProps<{ edgeId?: string }>> = ({ cancel, submit,
<Select
{...field}
{...nodeSelectProps}
className={cx(errors.target && "form-control react-select is-invalid")}
isDisabled={!isNew}
onChange={(newValue) => onChange(newValue as NodeOption)}
id="updateEdge-target"
/>
)}
/>
{errors.target && <div className="invalid-feedback">{t(`error.form.${errors.target.type}`)}</div>}
</div>

{/* Rendering attributes */}
Expand All @@ -227,39 +245,44 @@ const UpdateEdgeModal: FC<ModalProps<{ edgeId?: string }>> = ({ cancel, submit,
<FaTimes />
</button>
</div>
{/*<div className="col-md-6 d-flex flex-row align-items-center">*/}
{/* <label htmlFor="updateEdge-color" className="form-label mb-0 flex-grow-1">*/}
{/* {t("graph.model.edges-data.color")}*/}
{/* </label>*/}
{/* <ColorPicker clearable color={watch("color")} onChange={(color) => setValue("color", color)} />*/}
{/* <button*/}
{/* type="button"*/}
{/* className="btn btn-sm btn-outline-dark flex-shrink-0 ms-2"*/}
{/* onClick={() => setValue("color", undefined)}*/}
{/* >*/}
{/* <FaTimes />*/}
{/* </button>*/}
{/*</div>*/}

{/* Other attributes */}
<div>{t("graph.model.edges-data.attributes")}</div>
{attributes.map((_, i) => (
<div key={i} className="col-12 d-flex flex-row">
<input
type="text"
className="form-control flex-grow-1 me-2"
placeholder={t("graph.model.edges-data.attribute-name") as string}
{...register(`attributes.${i}.key`, {
required: "true",
validate: (value, formValues) => !formValues.attributes.some((v, j) => j !== i && value === v.key),
})}
/>
<input
type="text"
className="form-control flex-grow-1 me-2"
placeholder={t("graph.model.edges-data.attribute-value") as string}
{...register(`attributes.${i}.value`)}
/>
<div className="flex-grow-1 me-2">
<input
type="text"
className="form-control"
placeholder={t("graph.model.edges-data.attribute-name") as string}
{...register(`attributes.${i}.key`, {
required: "true",
validate: (value, formValues) => !formValues.attributes.some((v, j) => j !== i && value === v.key),
})}
/>
{(errors.attributes || [])[i]?.key && (
<div className="invalid-feedback">
{t(
`error.form.${
(errors.attributes || [])[i]?.key?.type === "validate"
? "unique"
: (errors.attributes || [])[i]?.key?.type
}`,
)}
</div>
)}
</div>
<div className="flex-grow-1 me-2">
<input
type="text"
className="form-control"
placeholder={t("graph.model.edges-data.attribute-value") as string}
{...register(`attributes.${i}.value`)}
/>
{(errors.attributes || [])[i]?.value && (
<div className="invalid-feedback">{t(`error.form.${(errors.attributes || [])[i]?.value?.type}`)}</div>
)}
</div>
<button
type="button"
className="btn btn-sm btn-outline-dark flex-shrink-0"
Expand Down
106 changes: 51 additions & 55 deletions src/views/graphPage/modals/edition/UpdateNodeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { useTranslation } from "react-i18next";
import { AiOutlinePlusCircle } from "react-icons/ai";
import { BsFillTrashFill } from "react-icons/bs";
import { FaTimes } from "react-icons/fa";
import cx from "classnames";

import { Modal } from "../../../../components/modals";
import { useGraphDataset, useGraphDatasetActions, useSelectionActions } from "../../../../core/context/dataContexts";
import { NodeRenderingData } from "../../../../core/graph/types";
import { ModalProps } from "../../../../core/modals/types";
import { useNotifications } from "../../../../core/notifications";
import { toNumber } from "../../../../core/utils/casting";
// import ColorPicker from "../../../../components/ColorPicker";

interface UpdatedNodeState extends Omit<NodeRenderingData, "rawSize"> {
id: string;
Expand All @@ -22,7 +22,6 @@ interface UpdatedNodeState extends Omit<NodeRenderingData, "rawSize"> {
const UpdateNodeModal: FC<ModalProps<{ nodeId?: string }>> = ({ cancel, submit, arguments: { nodeId } }) => {
const { t } = useTranslation();
const { notify } = useNotifications();

const { createNode, updateNode } = useGraphDatasetActions();
const { nodeData, nodeRenderingData } = useGraphDataset();
const { select } = useSelectionActions();
Expand All @@ -45,7 +44,14 @@ const UpdateNodeModal: FC<ModalProps<{ nodeId?: string }>> = ({ cancel, submit,
})),
};
}, [isNew, nodeId, nodeRenderingData, nodeData]);
const { register, handleSubmit, setValue, getValues, watch } = useForm<UpdatedNodeState>({
const {
register,
handleSubmit,
setValue,
getValues,
watch,
formState: { errors },
} = useForm<UpdatedNodeState>({
defaultValues,
});
const attributes = watch("attributes");
Expand Down Expand Up @@ -116,10 +122,15 @@ const UpdateNodeModal: FC<ModalProps<{ nodeId?: string }>> = ({ cancel, submit,
<input
type="text"
id="updateNode-id"
className="form-control"
className={cx("form-control", errors.id && "is-invalid")}
disabled={!isNew}
{...register("id", { required: "true", validate: (value) => !!value && (!isNew || !nodeData[value]) })}
/>
{errors.id && (
<div className="invalid-feedback">
{t(`error.form.${errors.id.type === "validate" ? "unique" : errors.id.type}`)}
</div>
)}
</div>

{/* Rendering attributes */}
Expand All @@ -130,7 +141,7 @@ const UpdateNodeModal: FC<ModalProps<{ nodeId?: string }>> = ({ cancel, submit,
<input
type="text"
id="updateNode-label"
className="form-control flex-grow-1 ms-2"
className={cx("form-control flex-grow-1 ms-2", errors.label && "is-invalid")}
min={0}
{...register("label")}
/>
Expand All @@ -142,47 +153,14 @@ const UpdateNodeModal: FC<ModalProps<{ nodeId?: string }>> = ({ cancel, submit,
<FaTimes />
</button>
</div>
{/*<div className="col-md-4 d-flex flex-row align-items-center">*/}
{/* <label htmlFor="updateNode-color" className="form-label mb-0 flex-grow-1">*/}
{/* {t("graph.model.nodes-data.color")}*/}
{/* </label>*/}
{/* <ColorPicker clearable color={watch("color")} onChange={(color) => setValue("color", color)} />*/}
{/* <button*/}
{/* type="button"*/}
{/* className="btn btn-sm btn-outline-dark flex-shrink-0 ms-2"*/}
{/* onClick={() => setValue("color", undefined)}*/}
{/* >*/}
{/* <FaTimes />*/}
{/* </button>*/}
{/*</div>*/}
{/*<div className="col-md-4 d-flex flex-row align-items-center">*/}
{/* <label htmlFor="updateNode-size" className="form-label mb-0 flex-shrink-0">*/}
{/* {t("graph.model.nodes-data.size")}*/}
{/* </label>*/}
{/* <input*/}
{/* type="number"*/}
{/* id="updateNode-size"*/}
{/* className="form-control flex-grow-1 ms-2"*/}
{/* step="any"*/}
{/* min={0}*/}
{/* {...register("size", { min: 0 })}*/}
{/* />*/}
{/* <button*/}
{/* type="button"*/}
{/* className="btn btn-sm btn-outline-dark flex-shrink-0 ms-2"*/}
{/* onClick={() => setValue("size", undefined)}*/}
{/* >*/}
{/* <FaTimes />*/}
{/* </button>*/}
{/*</div>*/}
<div className="col-md-6 d-flex flex-row align-items-center">
<label htmlFor="updateNode-x" className="form-label mb-0 flex-shrink-0">
{t("graph.model.nodes-data.x")}
</label>
<input
type="number"
id="updateNode-x"
className="form-control flex-grow-1 ms-2"
className={cx("form-control flex-grow-1 ms-2", errors.x && "is-invalid")}
step="any"
{...register("x")}
/>
Expand All @@ -194,7 +172,7 @@ const UpdateNodeModal: FC<ModalProps<{ nodeId?: string }>> = ({ cancel, submit,
<input
type="number"
id="updateNode-y"
className="form-control flex-grow-1 ms-2"
className={cx("form-control flex-grow-1 ms-2", errors.y && "is-invalid")}
step="any"
{...register("y")}
/>
Expand All @@ -204,21 +182,39 @@ const UpdateNodeModal: FC<ModalProps<{ nodeId?: string }>> = ({ cancel, submit,
<div className="fs-5">{t("graph.model.nodes-data.attributes")}</div>
{attributes.map((_, i) => (
<div key={i} className="col-12 d-flex flex-row">
<input
type="text"
className="form-control flex-grow-1 me-2"
placeholder={t("graph.model.nodes-data.attribute-name") as string}
{...register(`attributes.${i}.key`, {
required: "true",
validate: (value, formValues) => !formValues.attributes.some((v, j) => j !== i && value === v.key),
})}
/>
<input
type="text"
className="form-control flex-grow-1 me-2"
placeholder={t("graph.model.nodes-data.attribute-value") as string}
{...register(`attributes.${i}.value`)}
/>
<div className="flex-grow-1 me-2">
<input
type="text"
className={cx("form-control", (errors.attributes || [])[i]?.key && "is-invalid")}
placeholder={t("graph.model.nodes-data.attribute-name") as string}
{...register(`attributes.${i}.key`, {
required: "true",
validate: (value, formValues) => !formValues.attributes.some((v, j) => j !== i && value === v.key),
})}
/>
{(errors.attributes || [])[i]?.key && (
<div className="invalid-feedback">
{t(
`error.form.${
(errors.attributes || [])[i]?.key?.type === "validate"
? "unique"
: (errors.attributes || [])[i]?.key?.type
}`,
)}
</div>
)}
</div>
<div className="flex-grow-1 me-2">
<input
type="text"
className={cx("form-control flex-grow-1 me-2", (errors.attributes || [])[i]?.value && "is-invalid")}
placeholder={t("graph.model.nodes-data.attribute-value") as string}
{...register(`attributes.${i}.value`)}
/>
{(errors.attributes || [])[i]?.value && (
<div className="invalid-feedback">{t(`error.form.${(errors.attributes || [])[i]?.value?.type}`)}</div>
)}
</div>
<button
type="button"
className="btn btn-sm btn-outline-dark flex-shrink-0"
Expand Down

0 comments on commit 68d6a04

Please sign in to comment.