diff --git a/src/apps/schema/src/appRevamp/components/AddFieldModal/FieldFormInput.tsx b/src/apps/schema/src/appRevamp/components/AddFieldModal/FieldFormInput.tsx
index a898cd6b83..12a798c7c6 100644
--- a/src/apps/schema/src/appRevamp/components/AddFieldModal/FieldFormInput.tsx
+++ b/src/apps/schema/src/appRevamp/components/AddFieldModal/FieldFormInput.tsx
@@ -9,6 +9,8 @@ import {
InputBase,
} from "@mui/material";
+import { FormValue } from "./views/FieldForm";
+
type FieldType = "input" | "checkbox" | "dropdown";
export interface InputField {
name: string;
@@ -22,18 +24,14 @@ export interface InputField {
interface Props {
fieldConfig: InputField;
errorMsg?: string;
- onDataChange: ({
- name,
- value,
- }: {
- name: string;
- value: string | boolean;
- }) => void;
+ onDataChange: ({ name, value }: { name: string; value: FormValue }) => void;
+ prefillData?: FormValue;
}
export const FieldFormInput = ({
fieldConfig,
errorMsg,
onDataChange,
+ prefillData,
}: Props) => {
return (
@@ -78,6 +76,7 @@ export const FieldFormInput = ({
value: e.target.value,
});
}}
+ value={prefillData}
/>
{errorMsg && (
@@ -100,6 +99,7 @@ export const FieldFormInput = ({
value: e.target.checked,
});
}}
+ checked={Boolean(prefillData)}
/>
}
label={
diff --git a/src/apps/schema/src/appRevamp/components/AddFieldModal/index.tsx b/src/apps/schema/src/appRevamp/components/AddFieldModal/index.tsx
index ada7d92fd7..ad0067446b 100644
--- a/src/apps/schema/src/appRevamp/components/AddFieldModal/index.tsx
+++ b/src/apps/schema/src/appRevamp/components/AddFieldModal/index.tsx
@@ -1,21 +1,33 @@
-import { Dispatch, SetStateAction, useState } from "react";
+import { Dispatch, SetStateAction, useState, useMemo } from "react";
+import { useParams } from "react-router";
import { Dialog } from "@mui/material";
import { FieldSelection } from "./views/FieldSelection";
import { FieldForm } from "./views/FieldForm";
-import { ContentModelField } from "../../../../../../shell/services/types";
+import { useGetContentModelFieldsQuery } from "../../../../../../shell/services/instance";
+type Params = {
+ id: string;
+ fieldId: string;
+};
export type ViewMode = "fields_list" | "new_field" | "update_field";
interface Props {
onModalClose: Dispatch>;
- fields: ContentModelField[];
+ mode: ViewMode;
}
-export const AddFieldModal = ({ onModalClose, fields }: Props) => {
- const [viewMode, setViewMode] = useState("fields_list");
+export const AddFieldModal = ({ onModalClose, mode }: Props) => {
+ const [viewMode, setViewMode] = useState(mode);
const [selectedField, setSelectedField] = useState({
fieldType: "",
fieldName: "",
});
+ const params = useParams();
+ const { id, fieldId } = params;
+ const { data: fields } = useGetContentModelFieldsQuery(id);
+
+ const fieldData = useMemo(() => {
+ return fields?.find((field) => field.ZUID === fieldId);
+ }, [fieldId, fields]);
const handleFieldClick = (fieldType: string, fieldName: string) => {
setViewMode("new_field");
@@ -57,6 +69,16 @@ export const AddFieldModal = ({ onModalClose, fields }: Props) => {
onFieldCreationSuccesssful={() => onModalClose(false)}
/>
)}
+ {viewMode === "update_field" && (
+ onModalClose(false)}
+ onFieldCreationSuccesssful={() => onModalClose(false)}
+ fieldData={fieldData}
+ />
+ )}
);
};
diff --git a/src/apps/schema/src/appRevamp/components/AddFieldModal/views/FieldForm.tsx b/src/apps/schema/src/appRevamp/components/AddFieldModal/views/FieldForm.tsx
index 50bf537195..0c41934884 100644
--- a/src/apps/schema/src/appRevamp/components/AddFieldModal/views/FieldForm.tsx
+++ b/src/apps/schema/src/appRevamp/components/AddFieldModal/views/FieldForm.tsx
@@ -10,8 +10,10 @@ import {
Tabs,
Tab,
Button,
+ CircularProgress,
} from "@mui/material";
-import { snakeCase } from "lodash";
+import LoadingButton from "@mui/lab/LoadingButton";
+import { snakeCase, isEmpty } from "lodash";
import CloseIcon from "@mui/icons-material/Close";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
@@ -20,8 +22,15 @@ import AddRoundedIcon from "@mui/icons-material/AddRounded";
import { FieldIcon } from "../../Field/FieldIcon";
import { stringStartsWithVowel } from "../../utils";
import { InputField, FieldFormInput } from "../FieldFormInput";
-import { useCreateContentModelFieldMutation } from "../../../../../../../shell/services/instance";
-import { ContentModelField } from "../../../../../../../shell/services/types";
+import {
+ useCreateContentModelFieldMutation,
+ useUpdateContentModelFieldMutation,
+} from "../../../../../../../shell/services/instance";
+import {
+ ContentModelField,
+ FieldSettings,
+ ContentModelFieldValue,
+} from "../../../../../../../shell/services/types";
const commonFields: InputField[] = [
{
@@ -88,8 +97,9 @@ type ActiveTab = "details" | "rules";
type Params = {
id: string;
};
+export type FormValue = Exclude;
interface FormData {
- [key: string]: string | boolean;
+ [key: string]: FormValue;
}
interface Errors {
[key: string]: string;
@@ -98,9 +108,10 @@ interface Props {
type: string;
name: string;
onModalClose: () => void;
- onBackClick: () => void;
+ onBackClick?: () => void;
fields: ContentModelField[];
onFieldCreationSuccesssful: () => void;
+ fieldData?: ContentModelField;
}
export const FieldForm = ({
type,
@@ -109,6 +120,7 @@ export const FieldForm = ({
onBackClick,
fields,
onFieldCreationSuccesssful,
+ fieldData,
}: Props) => {
const [activeTab, setActiveTab] = useState("details");
const [isSubmitClicked, setIsSubmitClicked] = useState(false);
@@ -116,31 +128,54 @@ export const FieldForm = ({
const [formData, setFormData] = useState({});
const params = useParams();
const { id } = params;
- const [createContentModelField, { isLoading, isSuccess }] =
- useCreateContentModelFieldMutation();
+ const [
+ createContentModelField,
+ { isLoading: isCreatingField, isSuccess: isFieldCreated },
+ ] = useCreateContentModelFieldMutation();
+ const [
+ updateContentModelField,
+ { isLoading: isUpdatingField, isSuccess: isFieldUpdated },
+ ] = useUpdateContentModelFieldMutation();
+ const isUpdateField = !isEmpty(fieldData);
useEffect(() => {
- let formFields: { [key: string]: string | boolean } = {};
+ let formFields: { [key: string]: FormValue } = {};
let errors: { [key: string]: string } = {};
- formConfig[type].forEach((field) => {
- formFields[field.name] = field.type === "checkbox" ? false : "";
+ formConfig[type]?.forEach((field) => {
+ if (isUpdateField) {
+ if (field.name === "list") {
+ formFields[field.name] = fieldData.settings[field.name];
+ } else {
+ formFields[field.name] = fieldData[field.name] as FormValue;
+ }
- if (field.required) {
- errors[field.name] = "This field is required";
+ // Pre-fill error messages based on content
+ if (field.required) {
+ errors[field.name] = isEmpty(fieldData[field.name])
+ ? "This field is required"
+ : "";
+ }
+ } else {
+ formFields[field.name] = field.type === "checkbox" ? false : "";
+
+ // Pre-fill required fields error msgs
+ if (field.required) {
+ errors[field.name] = "This field is required";
+ }
}
});
setFormData(formFields);
setErrors(errors);
- }, [type]);
+ }, [type, fieldData]);
useEffect(() => {
// TODO: Field creation flow is not yet completed, closing modal on success for now
- if (isSuccess) {
+ if (isFieldCreated || isFieldUpdated) {
onFieldCreationSuccesssful();
}
- }, [isSuccess]);
+ }, [isFieldCreated, isFieldUpdated]);
const handleSubmitForm = () => {
setIsSubmitClicked(true);
@@ -163,10 +198,23 @@ export const FieldForm = ({
settings: {
list: formData.list as boolean,
},
- sort: fields?.length, // Just use the length since sort starts at 0
+ sort: isUpdateField ? fieldData.sort : fields?.length, // Just use the length since sort starts at 0
};
- createContentModelField({ modelZUID: id, body });
+ if (isUpdateField) {
+ const updateBody: ContentModelField = {
+ ...fieldData,
+ ...body,
+ };
+
+ updateContentModelField({
+ modelZUID: id,
+ fieldZUID: fieldData.ZUID,
+ body: updateBody,
+ });
+ } else {
+ createContentModelField({ modelZUID: id, body });
+ }
};
const handleFieldDataChange = ({
@@ -185,9 +233,17 @@ export const FieldForm = ({
let errorMsg = value ? "" : "This field is required";
if (value && name === "name") {
- errorMsg = currFieldNames.includes(value as string)
- ? "Field name already exists"
- : "";
+ if (isUpdateField) {
+ // Re-using its original name is fine when updating a field
+ errorMsg =
+ currFieldNames.includes(value as string) && value !== fieldData.name
+ ? "Field name already exists"
+ : "";
+ } else {
+ errorMsg = currFieldNames.includes(value as string)
+ ? "Field name already exists"
+ : "";
+ }
}
if (name in errors) {
@@ -217,9 +273,11 @@ export const FieldForm = ({
pb={0.5}
>
-
-
-
+ {!isUpdateField && (
+
+
+
+ )}
- {headerText}
+ {isUpdateField ? fieldData.label : headerText}
@@ -251,13 +309,14 @@ export const FieldForm = ({
>
{activeTab === "details" && (
<>
- {formConfig[type].map((fieldConfig, index) => {
+ {formConfig[type]?.map((fieldConfig, index) => {
return (
);
})}
@@ -290,13 +349,13 @@ export const FieldForm = ({
>
Add another field
-
+
>
diff --git a/src/apps/schema/src/appRevamp/components/Field/index.tsx b/src/apps/schema/src/appRevamp/components/Field/index.tsx
index 66863e96c4..7a561d937e 100644
--- a/src/apps/schema/src/appRevamp/components/Field/index.tsx
+++ b/src/apps/schema/src/appRevamp/components/Field/index.tsx
@@ -1,9 +1,25 @@
import React, { useRef, useState, useEffect } from "react";
-import { Box, IconButton, Typography, Button, Tooltip } from "@mui/material";
+import { useLocation, useHistory } from "react-router";
+import {
+ Box,
+ IconButton,
+ Typography,
+ Button,
+ Tooltip,
+ Menu,
+ MenuList,
+ MenuItem,
+ ListItemIcon,
+ ListItemText,
+} from "@mui/material";
import { ContentModelField } from "../../../../../../shell/services/types";
import DragIndicatorRoundedIcon from "@mui/icons-material/DragIndicatorRounded";
import MoreHorizRoundedIcon from "@mui/icons-material/MoreHorizRounded";
import CheckIcon from "@mui/icons-material/Check";
+import ContentCopyRoundedIcon from "@mui/icons-material/ContentCopyRounded";
+import DriveFileRenameOutlineRoundedIcon from "@mui/icons-material/DriveFileRenameOutlineRounded";
+import WidgetsRoundedIcon from "@mui/icons-material/WidgetsRounded";
+import HighlightOffRoundedIcon from "@mui/icons-material/HighlightOffRounded";
import { FieldIcon } from "./FieldIcon";
@@ -53,6 +69,10 @@ export const Field = ({
const [isDragging, setIsDragging] = useState(false);
const [isDraggable, setIsDraggable] = useState(false);
const [isFieldNameCopied, setIsFieldNameCopied] = useState(false);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const isMenuOpen = Boolean(anchorEl);
+ const location = useLocation();
+ const history = useHistory();
useEffect(() => {
let timeoutId: NodeJS.Timeout;
@@ -116,6 +136,10 @@ export const Field = ({
}
};
+ const handleMenuClick = (e: React.MouseEvent) => {
+ setAnchorEl(e.currentTarget);
+ };
+
const style = {
opacity: isDragging ? 0.01 : 1,
};
@@ -194,10 +218,49 @@ export const Field = ({
{isFieldNameCopied ? "Copied" : field.name}
- {/* TODO: More button click action handler, still pending confirmation from zosh on what will happen */}
-
+
+
);
diff --git a/src/apps/schema/src/appRevamp/components/ModelHeader/index.tsx b/src/apps/schema/src/appRevamp/components/ModelHeader/index.tsx
index ec06027221..34c8ac5723 100644
--- a/src/apps/schema/src/appRevamp/components/ModelHeader/index.tsx
+++ b/src/apps/schema/src/appRevamp/components/ModelHeader/index.tsx
@@ -30,8 +30,7 @@ export const ModelHeader = () => {
const { data: models } = useGetContentModelsQuery();
const location = useLocation();
const history = useHistory();
- const { data: fields, isSuccess: isFieldsLoaded } =
- useGetContentModelFieldsQuery(id);
+ const { isSuccess: isFieldsLoaded } = useGetContentModelFieldsQuery(id);
const model = models?.find((model) => model.ZUID === id);
@@ -84,7 +83,7 @@ export const ModelHeader = () => {
},
},
}}
- value={location.pathname.split("/").pop()}
+ value={location.pathname.split("/")[3]}
onChange={(event, value) =>
history.push(`/schema/${model?.ZUID}/${value}`)
}
@@ -105,7 +104,7 @@ export const ModelHeader = () => {
{isAddFieldModalOpen && (
-
+
)}
>
);
diff --git a/src/apps/schema/src/appRevamp/components/utils.ts b/src/apps/schema/src/appRevamp/components/utils.ts
index 656edf8039..30bfea1be1 100644
--- a/src/apps/schema/src/appRevamp/components/utils.ts
+++ b/src/apps/schema/src/appRevamp/components/utils.ts
@@ -1,4 +1,6 @@
export const stringStartsWithVowel = (string: string): boolean => {
+ if (!string) return;
+
const firstLetter = string[0];
return ["a", "e", "i", "o", "u"].includes(firstLetter.toLowerCase());
diff --git a/src/apps/schema/src/appRevamp/views/Model.tsx b/src/apps/schema/src/appRevamp/views/Model.tsx
index 64e0b43f98..59127eb553 100644
--- a/src/apps/schema/src/appRevamp/views/Model.tsx
+++ b/src/apps/schema/src/appRevamp/views/Model.tsx
@@ -1,15 +1,33 @@
import { Box } from "@mui/material";
-import { Redirect, Route, Switch, useParams } from "react-router";
+import { Redirect, Route, Switch, useParams, useHistory } from "react-router";
import { useGetContentModelsQuery } from "../../../../../shell/services/instance";
import { FieldList } from "../components/FieldList";
import { ModelHeader } from "../components/ModelHeader";
+import { AddFieldModal } from "../components/AddFieldModal";
+type Params = {
+ id: string;
+};
export const Model = () => {
+ const history = useHistory();
+ const params = useParams();
+ const { id } = params;
+
return (
} />
+ (
+ history.push(`/schema/${id}/fields`)}
+ />
+ )}
+ />
({
getItemPublishings: builder.query<
@@ -246,4 +247,5 @@ export const {
useBulkUpdateContentModelFieldMutation,
useUpdateContentModelMutation,
useCreateContentModelFieldMutation,
+ useUpdateContentModelFieldMutation,
} = instanceApi;
diff --git a/src/shell/services/types.ts b/src/shell/services/types.ts
index f8ad19981e..b7d9d5d78b 100644
--- a/src/shell/services/types.ts
+++ b/src/shell/services/types.ts
@@ -147,6 +147,8 @@ export interface FieldSettings {
tooltip?: string;
}
+export type ContentModelFieldValue = string | number | boolean | FieldSettings;
+
export interface ContentModelField {
ZUID: string;
contentModelZUID: string;
@@ -165,4 +167,5 @@ export interface ContentModelField {
relatedFieldZUID?: any;
createdAt: string;
updatedAt: string;
+ [key: string]: ContentModelFieldValue;
}