Author:
Christopher Tse
Changed on:
14 Oct 2024
| Name | RETURN_REASON_SCHEMA |
| Context | ACCOUNT |
| Context ID | 0 |
| Type | JSON |
| Value | {{schema from above}} |
`title` property. The value in the `title` property is passed to the field's `label` property, which controls how the title is displayed.Depending on the JSON schema generator you used, titles may already be present in your schema. In this example, only the top-level title was added initially, but we also need titles for the `label` and `value` fields. Titles for `object` types can be skipped, as they do not require labels. Here's an example:(Note: i18n strings are currently not supported)For schemas that already include titles, you can use this step to update them with more appropriate labels. Ensure that each field has a clear and descriptive title.We will later demonstrate how these titles are reflected in the settings component when we review how it is laid out on a page.`fc.mystique.manifest.oms.fragment.admin` setting. Since this doesn't exist by default, first retrieve the default manifest by referring to this guide. Once you’ve created the setting, the next step is to edit the Retailer details page to split it into two tabs. The first tab will be named Details and will retain the existing layout currently defined in the manifest. The second tab, called Settings, will host the new component for editing the return reasons.
Now it looks much better.`index.tsx`:4. Use the Custom Field in the Schema`fcField` keyword to your items, which allows you to specify the field by its name in the `name` property. The updated schema will look like this:5. Add UI to Your Field`title` from the schema. Then you can add a simple text field next to the label to capture the user's input to change or add a return reason.6. Handle Value and Label Logic`onChange` method. The label will come from the text field, and you will also initialize the text field with the existing label value if it exists.7. Managing Value State
`fcField`'s `extension` keyword. The extension is a dictionary of key-value pairs, allowing you to specify any keys and values you desire. Everything defined in the `extension` keyword will be passed to the field via its `extension` prop.Let's modify our field to include an `alwaysChangeValue` property:And here’s how the schema would look:Now we have a field that can adapt its behavior based on the `alwaysChangeValue` property in the schema.1{
2 "$schema": "http://json-schema.org/draft-07/schema#",
3 "title": "Generated schema for Root",
4 "type": "array",
5 "items": {
6 "type": "object",
7 "properties": {
8 "label": {
9 "type": "string"
10 },
11 "value": {
12 "type": "string"
13 }
14 },
15 "required": [
16 "label",
17 "value"
18 ]
19 }
20}1{
2 "title": "Return Reasons",
3 "type": "array",
4 "items": {
5 "type": "object",
6 "properties": {
7 "label": {
8 "title": "Label",
9 "type": "string"
10 },
11 "value": {
12 "title": "Value",
13 "type": "string"
14 }
15 }
16 }
17}1{
2 "path": "retailers/:id",
3 "type": "page",
4 "component": "fc.page",
5 "data": {
6 "query": "query ($id: ID!) { retailerById(id: $id) { id ref tradingName createdOn websiteUrlName websiteUrl supportContactName supportEmail primaryEmail supportPhone summary updatedOn }}",
7 "variables": {
8 "id": "{{params.id}}"
9 }
10 },
11 "props": {
12 "title": "Retailer - {{retailerById.ref}}",
13 "backButtons": [
14 {
15 "path": "retailers",
16 "menuLabel": "i18n:fc.admin.retailers.detail.breadcrumb.backToRetailers"
17 }
18 ]
19 },
20 "descendants": [
21 {
22 "component": "fc.tabs",
23 "props": {
24 "layouts": [
25 {
26 "label": "Summary"
27 },
28 {
29 "label": "Settings"
30 }
31 ]
32 },
33 "descendants": [
34 {
35 "component": "fc.page.section",
36 "descendants": [
37 {
38 "component": "fc.card.attribute",
39 "props": {
40 "title": "i18n:fc.admin.retailers.detail.card.summary.title",
41 "width": "half",
42 "dataSource": "retailerById",
43 "attributes": [
44 {
45 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.id.label",
46 "template": "{{id}}"
47 },
48 {
49 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.ref.label",
50 "template": "{{ref}}"
51 },
52 {
53 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.creationDate.label",
54 "template": "{{dateStringFormatter createdOn}} ({{dateRelative createdOn}})"
55 },
56 {
57 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.updatedDate.label",
58 "template": "{{dateStringFormatter updatedOn}} ({{dateRelative updatedOn}})"
59 }
60 ]
61 }
62 },
63 {
64 "component": "fc.card.attribute",
65 "props": {
66 "title": "i18n:fc.admin.retailers.detail.card.details.title",
67 "width": "half",
68 "dataSource": "retailerById",
69 "attributes": [
70 {
71 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.tradingName.label",
72 "template": "{{tradingName}}"
73 },
74 {
75 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.websiteUrl.label",
76 "template": "{{websiteUrl}}",
77 "link_template": "{{websiteUrl}}",
78 "condition": "{{and websiteUrl}}"
79 },
80 {
81 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.supportPhone.label",
82 "template": "{{supportPhone}}"
83 },
84 {
85 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.supportEmail.label",
86 "template": "{{supportEmail}}"
87 }
88 ]
89 }
90 }
91 ]
92 },
93 {
94 "component": "fc.page.section",
95 "descendants": [
96 {
97 "component": "fc.settingForm",
98 "props": {
99 "context": "RETAILER",
100 "contextId": "1",
101 "setting": "RETURN_REASON",
102 "schema": "RETURN_REASON_SCHEMA"
103 }
104 }
105 ]
106 }
107 ]
108 }
109 ]
110}1{
2 "path": "retailers/:id",
3 "type": "page",
4 "component": "fc.page",
5 "data": {
6 "query": "query ($id: ID!) { retailerById(id: $id) { id ref tradingName createdOn websiteUrlName websiteUrl supportContactName supportEmail primaryEmail supportPhone summary updatedOn }}",
7 "variables": {
8 "id": "{{params.id}}"
9 }
10 },
11 "props": {
12 "title": "Retailer - {{retailerById.ref}}",
13 "backButtons": [
14 {
15 "path": "retailers",
16 "menuLabel": "i18n:fc.admin.retailers.detail.breadcrumb.backToRetailers"
17 }
18 ]
19 },
20 "descendants": [
21 {
22 "component": "fc.tabs",
23 "props": {
24 "layouts": [
25 {
26 "label": "Summary"
27 },
28 {
29 "label": "Settings"
30 }
31 ]
32 },
33 "descendants": [
34 {
35 "component": "fc.page.section",
36 "descendants": [
37 {
38 "component": "fc.card.attribute",
39 "props": {
40 "title": "i18n:fc.admin.retailers.detail.card.summary.title",
41 "width": "half",
42 "dataSource": "retailerById",
43 "attributes": [
44 {
45 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.id.label",
46 "template": "{{id}}"
47 },
48 {
49 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.ref.label",
50 "template": "{{ref}}"
51 },
52 {
53 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.creationDate.label",
54 "template": "{{dateStringFormatter createdOn}} ({{dateRelative createdOn}})"
55 },
56 {
57 "label": "i18n:fc.admin.retailers.detail.card.summary.attribute.updatedDate.label",
58 "template": "{{dateStringFormatter updatedOn}} ({{dateRelative updatedOn}})"
59 }
60 ]
61 }
62 },
63 {
64 "component": "fc.card.attribute",
65 "props": {
66 "title": "i18n:fc.admin.retailers.detail.card.details.title",
67 "width": "half",
68 "dataSource": "retailerById",
69 "attributes": [
70 {
71 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.tradingName.label",
72 "template": "{{tradingName}}"
73 },
74 {
75 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.websiteUrl.label",
76 "template": "{{websiteUrl}}",
77 "link_template": "{{websiteUrl}}",
78 "condition": "{{and websiteUrl}}"
79 },
80 {
81 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.supportPhone.label",
82 "template": "{{supportPhone}}"
83 },
84 {
85 "label": "i18n:fc.admin.retailers.detail.card.details.attribute.supportEmail.label",
86 "template": "{{supportEmail}}"
87 }
88 ]
89 }
90 }
91 ]
92 },
93 {
94 "component": "fc.page.section",
95 "descendants": [
96 {
97 "component": "fc.page.section.column",
98 "props": {
99 "width": 2
100 }
101 },
102 {
103 "component": "fc.accordion",
104 "props": {
105 "width": 8,
106 "summary": "Return Reasons",
107 "details": {
108 "direction": "column",
109 "components": [
110 {
111 "component": "fc.settingForm",
112 "props": {
113 "context": "RETAILER",
114 "contextId": "1",
115 "setting": "RETURN_REASON",
116 "schema": "RETURN_REASON_SCHEMA"
117 }
118 }
119 ]
120 }
121 }
122 },
123 {
124 "component": "fc.page.section.column",
125 "props": {
126 "width": 2
127 }
128 }
129 ]
130 }
131 ]
132 }
133 ]
134}1{
2 "label": "string",
3 "value": "string"
4}1import { FormFieldProps } from 'mystique/registry/FieldRegistry';
2import { FC } from 'react';
3
4
5interface LabelValuePair {
6 label: string
7 value: string
8}
9
10export const LabelValuePairField : FC<FormFieldProps<LabelValuePair>> = ({onChange}) => {
11
12 return <></>;
13};1FieldRegistry.register(['labelValuePair'], LabelValuePairField);1{
2 "$schema": "http://json-schema.org/draft-07/schema#",
3 "title": "Return Reasons",
4 "type": "array",
5 "items": {
6 "title": "Return Reason",
7 "type": "object",
8 "fcField": {
9 "name": "labelValuePair"
10 },
11 "properties": {
12 "label": {
13 "title": "Label",
14 "description": "Put the label you want the business user to see here",
15 "type": "string"
16 },
17 "value": {
18 "title": "Value",
19 "description": "The internal code for a reason. This should stay constant whiel thelabel can change.",
20 "type": "string"
21 }
22 }
23 }
24}1import { css } from '@emotion/react';
2import { Box, TextField } from '@material-ui/core';
3import { FormFieldProps } from 'mystique/registry/FieldRegistry';
4import { FC } from 'react';
5
6interface LabelValuePair {
7 label: string
8 value: string
9}
10
11export const LabelValuePairField : FC<FormFieldProps<LabelValuePair>> = ({label, onChange}) => {
12 const boxCss = css`
13 display: flex;
14 align-items: center;
15 `;
16 const textFieldCss = css`
17 padding-left: 16px;
18 flex-grow: 1;
19 `;
20
21 return <Box css={boxCss}>
22 {label} <TextField css={textFieldCss} />
23 </Box>;
24};1import { css } from '@emotion/react';
2import { Box, TextField } from '@material-ui/core';
3import { FormFieldProps } from 'mystique/registry/FieldRegistry';
4import { FC } from 'react';
5
6interface LabelValuePair {
7 label: string
8 value: string
9}
10
11export const LabelValuePairField : FC<FormFieldProps<LabelValuePair>> = ({label, onChange, defaultValue}) => {
12 const boxCss = css`
13 display: flex;
14 align-items: center;
15 `;
16 const textFieldCss = css`
17 padding-left: 16px;
18 flex-grow: 1;
19 `;
20
21 const onTextChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
22 onChange({
23 label: e.target.value,
24 value: ''
25 });
26 };
27
28 return <Box css={boxCss}>
29 {label} <TextField css={textFieldCss} onChange={onTextChange} defaultValue={defaultValue?.label || ''}/>
30 </Box>;
31};1import { css } from '@emotion/react';
2import { Box, TextField } from '@material-ui/core';
3import { FormFieldProps } from 'mystique/registry/FieldRegistry';
4import { FC, useState } from 'react';
5
6interface LabelValuePair {
7 label: string
8 value: string
9}
10
11export const LabelValuePairField : FC<FormFieldProps<LabelValuePair>> = ({label, onChange, defaultValue}) => {
12 const [value] = useState(defaultValue?.value);
13 const boxCss = css`
14 display: flex;
15 align-items: center;
16 `;
17 const textFieldCss = css`
18 padding-left: 16px;
19 flex-grow: 1;
20 `;
21
22 const onTextChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
23 onChange({
24 label: e.target.value,
25 value: value ? value : e.target.value,
26 });
27 };
28
29 return <Box css={boxCss}>
30 {label} <TextField css={textFieldCss} onChange={onTextChange} defaultValue={defaultValue?.label || ''}/>
31 </Box>;
32};1import { css } from '@emotion/react';
2import { Box, TextField } from '@material-ui/core';
3import { FormFieldProps } from 'mystique/registry/FieldRegistry';
4import { FC, useState } from 'react';
5
6interface LabelValuePair {
7 label: string
8 value: string
9}
10
11export const LabelValuePairField : FC<FormFieldProps<LabelValuePair>> = ({label, onChange, defaultValue, extensions}) => {
12 const [value] = useState(defaultValue?.value);
13 const boxCss = css`
14 display: flex;
15 align-items: center;
16 `;
17 const textFieldCss = css`
18 padding-left: 16px;
19 flex-grow: 1;
20 `;
21
22 const onTextChange = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
23 const oldValue = value ? value : e.target.value;
24 const newValue = extensions?.alwaysChangeValue ? e.target.value : oldValue;
25 onChange({
26 label: e.target.value,
27 value: newValue,
28 });
29 };
30
31 return <Box css={boxCss}>
32 {label} <TextField css={textFieldCss} onChange={onTextChange} defaultValue={defaultValue?.label || ''}/>
33 </Box>;
34};1{
2 "title": "Return Reason:",
3 "type": "array",
4 "items": {
5 "title": "Return Reason",
6 "type": "object",
7 "fcField": {
8 "name": "labelvaluepair",
9 "extensions": {
10 "alwaysChangeValue": true
11 }
12 },
13 "properties": {
14 "label": {
15 "title": "Label",
16 "type": "string"
17 },
18 "value": {
19 "title": "Value",
20 "type": "string"
21 }
22 }
23 }
24}