@truedat/qx 5.17.1 → 5.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/package.json +3 -3
  2. package/src/api.js +24 -1
  3. package/src/components/QxRoutes.js +12 -5
  4. package/src/components/common/ClauseViewer.js +129 -0
  5. package/src/components/common/ResourceSelector.js +7 -2
  6. package/src/components/common/expressions/Clauses.js +15 -3
  7. package/src/components/common/expressions/Condition.js +8 -6
  8. package/src/components/common/expressions/ShapeSelector.js +18 -8
  9. package/src/components/common/expressions/__tests__/ShapeSelector.spec.js +2 -2
  10. package/src/components/dataViews/DataViewEditor.js +4 -3
  11. package/src/components/dataViews/__tests__/__snapshots__/DataViewEditor.spec.js.snap +2 -2
  12. package/src/components/dataViews/queryableFunctions.js +15 -9
  13. package/src/components/functions/FunctionEditor.js +3 -2
  14. package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +4 -4
  15. package/src/components/qualityControls/EditQualityControl.js +73 -0
  16. package/src/components/qualityControls/NewDraftQualityControl.js +77 -0
  17. package/src/components/qualityControls/NewQualityControl.js +81 -0
  18. package/src/components/qualityControls/QualityControl.js +93 -0
  19. package/src/components/qualityControls/QualityControlActions.js +67 -0
  20. package/src/components/qualityControls/QualityControlCrumbs.js +23 -0
  21. package/src/components/qualityControls/QualityControlEditor.js +271 -0
  22. package/src/components/qualityControls/QualityControlHeader.js +64 -0
  23. package/src/components/qualityControls/QualityControlHistory.js +81 -0
  24. package/src/components/qualityControls/QualityControlRoutes.js +84 -0
  25. package/src/components/qualityControls/QualityControlRow.js +24 -0
  26. package/src/components/qualityControls/QualityControlTabs.js +34 -0
  27. package/src/components/qualityControls/QualityControls.js +67 -0
  28. package/src/components/qualityControls/QualityControlsTable.js +139 -0
  29. package/src/components/qualityControls/ResultCriteria.js +120 -0
  30. package/src/components/qualityControls/ResultType.js +57 -0
  31. package/src/components/qualityControls/resultCriterias/Deviation.js +89 -0
  32. package/src/components/qualityControls/resultCriterias/ErrorsNumber.js +88 -0
  33. package/src/components/qualityControls/resultCriterias/Percentage.js +89 -0
  34. package/src/components/search/FilterDropdown.js +76 -0
  35. package/src/components/search/FilterItem.js +49 -0
  36. package/src/components/search/FilterMultilevelDropdown.js +200 -0
  37. package/src/components/search/HierarchyFilterDropdown.js +116 -0
  38. package/src/components/search/QualityControlFilters.js +60 -0
  39. package/src/components/search/QualityControlSelectedFilters.js +56 -0
  40. package/src/components/search/QualityControlsSearch.js +30 -0
  41. package/src/components/search/SearchContext.js +180 -0
  42. package/src/hooks/useQualityControls.js +74 -0
  43. package/src/styles/Expression.less +39 -4
@@ -0,0 +1,81 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext } from "react";
3
+ import { FormattedMessage } from "react-intl";
4
+ import { Table, Header, Icon } from "semantic-ui-react";
5
+ import { useIntl } from "react-intl";
6
+ import { columnDecorator } from "@truedat/core/services";
7
+
8
+ import QxContext from "../QxContext";
9
+
10
+ const translateDecorator = (id) =>
11
+ id ? <FormattedMessage id={id} defaultMessage={id} /> : null;
12
+
13
+ export default function QualityControlsHistory() {
14
+ const { qualityControl, loading } = useContext(QxContext);
15
+ const versions = _.propOr([], "versions")(qualityControl);
16
+
17
+ const columns = [
18
+ {
19
+ name: "name",
20
+ },
21
+ {
22
+ name: "version",
23
+ },
24
+ {
25
+ name: "status",
26
+ fieldSelector: ({ status }) =>
27
+ translateDecorator(`quality_control.status.${status}`),
28
+ width: 2,
29
+ },
30
+ ];
31
+
32
+ const { formatMessage } = useIntl();
33
+ return (
34
+ <>
35
+ {!_.isEmpty(versions) && (
36
+ <Table sortable>
37
+ <Table.Header>
38
+ <Table.Row>
39
+ {columns
40
+ ? columns.map((column, key) => (
41
+ <Table.HeaderCell
42
+ key={key}
43
+ width={column.width}
44
+ content={formatMessage({
45
+ id: `quality_control.form.${
46
+ column.header || column.name
47
+ }`,
48
+ defaultMessage: column.name,
49
+ })}
50
+ className={_.path("sort.name")(column) ? "" : "disabled"}
51
+ />
52
+ ))
53
+ : null}
54
+ </Table.Row>
55
+ </Table.Header>
56
+ <Table.Body>
57
+ {versions.map((version, i) => (
58
+ <Table.Row key={i}>
59
+ {columns.map((column, i) => (
60
+ <Table.Cell
61
+ key={i}
62
+ textAlign={column.textAlign}
63
+ content={columnDecorator(column)(version)}
64
+ />
65
+ ))}
66
+ </Table.Row>
67
+ ))}
68
+ </Table.Body>
69
+ </Table>
70
+ )}
71
+ {_.isEmpty(versions) && !loading && (
72
+ <Header as="h4">
73
+ <Icon name="search" />
74
+ <Header.Content>
75
+ {formatMessage({ id: "ruleImplementations.search.results.empty" })}
76
+ </Header.Content>
77
+ </Header>
78
+ )}
79
+ </>
80
+ );
81
+ }
@@ -0,0 +1,84 @@
1
+ import React from "react";
2
+ import { Route, Switch } from "react-router-dom";
3
+ import { Unauthorized } from "@truedat/core/components";
4
+ import { useAuthorized } from "@truedat/core/hooks";
5
+ import {
6
+ QUALITY_CONTROLS,
7
+ QUALITY_CONTROL_NEW,
8
+ QUALITY_CONTROL_EDIT,
9
+ QUALITY_CONTROL_NEW_DRAFT,
10
+ QUALITY_CONTROL,
11
+ } from "@truedat/core/routes";
12
+ import { QUALITY_CONTROL_HISTORY } from "../../../../core/src/routes";
13
+ import { SearchContextProvider } from "../search/SearchContext";
14
+ import QualityControls from "./QualityControls";
15
+ import QualityControl from "./QualityControl";
16
+ import QualityControlHeader from "./QualityControlHeader";
17
+ import QualityControlHistory from "./QualityControlHistory";
18
+ import NewQualityControl from "./NewQualityControl";
19
+ import EditQualityControl from "./EditQualityControl";
20
+ import NewDraftQualityControl from "./NewDraftQualityControl";
21
+
22
+ export default function QxRoutes() {
23
+ const authorized = useAuthorized("quality_control");
24
+
25
+ return (
26
+ <SearchContextProvider
27
+ initialSortColumn="updated_at"
28
+ initialSortDirection="descending"
29
+ >
30
+ <Switch>
31
+ <Route
32
+ exact
33
+ path={QUALITY_CONTROLS}
34
+ render={() => (authorized ? <QualityControls /> : <Unauthorized />)}
35
+ />
36
+ <Route
37
+ exact
38
+ path={QUALITY_CONTROL_NEW}
39
+ render={() => (authorized ? <NewQualityControl /> : <Unauthorized />)}
40
+ />
41
+ <Route
42
+ exact
43
+ path={QUALITY_CONTROL_EDIT}
44
+ render={() =>
45
+ authorized ? <EditQualityControl /> : <Unauthorized />
46
+ }
47
+ />
48
+ <Route
49
+ exact
50
+ path={QUALITY_CONTROL_NEW_DRAFT}
51
+ render={() =>
52
+ authorized ? <NewDraftQualityControl /> : <Unauthorized />
53
+ }
54
+ />
55
+ <Route
56
+ exact
57
+ path={QUALITY_CONTROL}
58
+ render={() =>
59
+ authorized ? (
60
+ <QualityControlHeader>
61
+ <QualityControl />
62
+ </QualityControlHeader>
63
+ ) : (
64
+ <Unauthorized />
65
+ )
66
+ }
67
+ />
68
+ <Route
69
+ exact
70
+ path={QUALITY_CONTROL_HISTORY}
71
+ render={() =>
72
+ authorized ? (
73
+ <QualityControlHeader>
74
+ <QualityControlHistory />
75
+ </QualityControlHeader>
76
+ ) : (
77
+ <Unauthorized />
78
+ )
79
+ }
80
+ />
81
+ </Switch>
82
+ </SearchContextProvider>
83
+ );
84
+ }
@@ -0,0 +1,24 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Table } from "semantic-ui-react";
5
+ import { columnDecorator } from "@truedat/core/services";
6
+
7
+ export default function QualityControlRow({ columns, qualityControl }) {
8
+ return _.isEmpty(columns) || _.isEmpty(qualityControl) ? null : (
9
+ <Table.Row>
10
+ {columns.map((column, i) => (
11
+ <Table.Cell
12
+ key={i}
13
+ textAlign={column.textAlign}
14
+ content={columnDecorator(column)(qualityControl)}
15
+ />
16
+ ))}
17
+ </Table.Row>
18
+ );
19
+ }
20
+
21
+ QualityControlRow.propTypes = {
22
+ columns: PropTypes.array,
23
+ qualityControl: PropTypes.object,
24
+ };
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import { Menu } from "semantic-ui-react";
3
+ import { Link, useRouteMatch, useParams } from "react-router-dom";
4
+ import { FormattedMessage } from "react-intl";
5
+ import {
6
+ QUALITY_CONTROL,
7
+ QUALITY_CONTROL_HISTORY,
8
+ linkTo,
9
+ } from "@truedat/core/routes";
10
+
11
+ export default function QualityControlTabs() {
12
+ const match = useRouteMatch();
13
+ const { id } = useParams();
14
+
15
+ return (
16
+ <Menu attached="top" pointing secondary tabular>
17
+ <Menu.Item
18
+ active={match.path === QUALITY_CONTROL}
19
+ as={Link}
20
+ to={linkTo.QUALITY_CONTROL({ id })}
21
+ >
22
+ <FormattedMessage id="quality_control.tabs.quality_control" />
23
+ </Menu.Item>
24
+
25
+ <Menu.Item
26
+ active={match.path === QUALITY_CONTROL_HISTORY}
27
+ as={Link}
28
+ to={linkTo.QUALITY_CONTROL_HISTORY({ id })}
29
+ >
30
+ <FormattedMessage id="quality_control.tabs.history" />
31
+ </Menu.Item>
32
+ </Menu>
33
+ );
34
+ }
@@ -0,0 +1,67 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { useIntl, FormattedMessage } from "react-intl";
4
+ import { Link } from "react-router-dom";
5
+ import {
6
+ Button,
7
+ Container,
8
+ Header,
9
+ Icon,
10
+ Segment,
11
+ Grid,
12
+ Placeholder,
13
+ } from "semantic-ui-react";
14
+ import { QUALITY_CONTROL_NEW } from "@truedat/core/routes";
15
+ import useAuthorizedAction from "@truedat/core/hooks/useAuthorizedAction";
16
+ import Loading from "@truedat/core/src/components/Loading";
17
+ import QualityControlsSearch from "../search/QualityControlsSearch";
18
+ import QualityControlsTable from "./QualityControlsTable";
19
+
20
+ export default function QualityControls() {
21
+ const { formatMessage } = useIntl();
22
+
23
+ const { data, loading } = useAuthorizedAction({
24
+ action: "createQualityControls",
25
+ });
26
+ const canCreate = _.prop("has_any_domain")(data);
27
+ return (
28
+ <Segment loading={false}>
29
+ <Header as="h2">
30
+ <Icon circular name="archive" />
31
+ <Header.Content>
32
+ <FormattedMessage id="quality_controls.header" />
33
+ <Header.Subheader>
34
+ <FormattedMessage id="quality_controls.subheader" />
35
+ </Header.Subheader>
36
+ </Header.Content>
37
+ </Header>
38
+ <Grid>
39
+ <Grid.Column width={8}>
40
+ <QualityControlsSearch />
41
+ </Grid.Column>
42
+
43
+ <Grid.Column width={8}>
44
+ <Container textAlign="right">
45
+ {loading ? (
46
+ <Loading />
47
+ ) : (
48
+ <>
49
+ {canCreate ? (
50
+ <Button
51
+ primary
52
+ as={Link}
53
+ to={QUALITY_CONTROL_NEW}
54
+ content={formatMessage({
55
+ id: "quality_controls.actions.create",
56
+ })}
57
+ />
58
+ ) : null}
59
+ </>
60
+ )}
61
+ </Container>
62
+ </Grid.Column>
63
+ </Grid>
64
+ <QualityControlsTable />
65
+ </Segment>
66
+ );
67
+ }
@@ -0,0 +1,139 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Link } from "react-router-dom";
5
+ import { sortColumn as sortHandler } from "@truedat/core/services/sort";
6
+ import { useIntl, FormattedMessage } from "react-intl";
7
+ import { Table, Header, Icon } from "semantic-ui-react";
8
+ import { linkTo } from "@truedat/core/routes";
9
+ import Moment from "react-moment";
10
+
11
+ import { useSearchContext } from "../search/SearchContext";
12
+ import QualityControlRow from "./QualityControlRow";
13
+
14
+ const translateDecorator = (id) =>
15
+ id ? <FormattedMessage id={id} defaultMessage={id} /> : null;
16
+
17
+ const DateDecorator = ({ date }) => {
18
+ const { locale } = useIntl();
19
+ return date ? (
20
+ <Moment locale={locale} date={date} format="YYYY-MM-DD HH:mm" />
21
+ ) : null;
22
+ };
23
+
24
+ DateDecorator.propTypes = {
25
+ date: PropTypes.string,
26
+ };
27
+
28
+ export default function QualityControlsTable() {
29
+ const {
30
+ qualityControls,
31
+ loadingSearch: loading,
32
+ sortColumn,
33
+ sortDirection,
34
+ setSortColumn,
35
+ setSortDirection,
36
+ } = useSearchContext();
37
+ const { formatMessage } = useIntl();
38
+
39
+ const columns = [
40
+ {
41
+ name: "name",
42
+ fieldSelector: _.pick(["id", "name"]),
43
+ fieldDecorator: ({ id, name }) => (
44
+ <Link to={linkTo.QUALITY_CONTROL({ id })}>{name}</Link>
45
+ ),
46
+ },
47
+ {
48
+ name: "df_type",
49
+ sort: { name: "df_type" },
50
+ width: 2,
51
+ },
52
+ {
53
+ name: "result_type",
54
+ sort: { name: "result_type" },
55
+ fieldSelector: ({ result_type }) =>
56
+ result_type
57
+ ? translateDecorator(`quality_control.result_type.${result_type}`)
58
+ : "-",
59
+ width: 2,
60
+ },
61
+ {
62
+ name: "status",
63
+ sort: { name: "status" },
64
+ fieldSelector: ({ status }) =>
65
+ translateDecorator(`quality_control.status.${status}`),
66
+ width: 2,
67
+ },
68
+ {
69
+ name: "updated_at",
70
+ sort: { name: "updated_at" },
71
+ fieldSelector: ({ updated_at }) => ({
72
+ date: updated_at,
73
+ }),
74
+ width: 2,
75
+ fieldDecorator: DateDecorator,
76
+ textAlign: "center",
77
+ },
78
+ ];
79
+
80
+ return (
81
+ <>
82
+ {!_.isEmpty(qualityControls) && (
83
+ <Table sortable>
84
+ <Table.Header>
85
+ <Table.Row>
86
+ {columns
87
+ ? columns.map((column, key) => (
88
+ <Table.HeaderCell
89
+ key={key}
90
+ width={column.width}
91
+ content={formatMessage({
92
+ id: `quality_control.form.${
93
+ column.header || column.name
94
+ }`,
95
+ defaultMessage: column.name,
96
+ })}
97
+ sorted={
98
+ _.path("sort.name")(column) === sortColumn
99
+ ? sortDirection
100
+ : null
101
+ }
102
+ className={_.path("sort.name")(column) ? "" : "disabled"}
103
+ onClick={() =>
104
+ sortHandler(
105
+ column,
106
+ () => {},
107
+ setSortDirection,
108
+ setSortColumn,
109
+ sortDirection,
110
+ sortColumn
111
+ )
112
+ }
113
+ />
114
+ ))
115
+ : null}
116
+ </Table.Row>
117
+ </Table.Header>
118
+ <Table.Body>
119
+ {qualityControls.map((qc, i) => (
120
+ <QualityControlRow
121
+ key={i}
122
+ qualityControl={qc}
123
+ columns={columns}
124
+ />
125
+ ))}
126
+ </Table.Body>
127
+ </Table>
128
+ )}
129
+ {_.isEmpty(qualityControls) && !loading && (
130
+ <Header as="h4">
131
+ <Icon name="search" />
132
+ <Header.Content>
133
+ {formatMessage({ id: "ruleImplementations.search.results.empty" })}
134
+ </Header.Content>
135
+ </Header>
136
+ )}
137
+ </>
138
+ );
139
+ }
@@ -0,0 +1,120 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { FormattedMessage } from "react-intl";
4
+ import { Header, Icon, Segment, List } from "semantic-ui-react";
5
+
6
+ export default function ResultCriteria({ qualityControl }) {
7
+ const { result_criteria, result_type } = qualityControl;
8
+ const resultCriteriaForType = {
9
+ percentage: <ResultCriteriaPercentage resultCriteria={result_criteria} />,
10
+ deviation: <ResultCriteriaDeviation resultCriteria={result_criteria} />,
11
+ errors_number: (
12
+ <ResultCriteriaErrorsNumber resultCriteria={result_criteria} />
13
+ ),
14
+ };
15
+
16
+ return (
17
+ <>
18
+ <Header as="h3">
19
+ <Icon name="flag checkered" size="small" />
20
+ <Header.Content>
21
+ <FormattedMessage id="quality_control.result_criteria" />
22
+ </Header.Content>
23
+ </Header>
24
+ <Segment>
25
+ {result_criteria ? (
26
+ <List>
27
+ <List.Item>
28
+ <List.Header>
29
+ <FormattedMessage id="quality.thresholds" />
30
+ </List.Header>
31
+ <List.Content>
32
+ <List.Description>
33
+ <FormattedMessage
34
+ id={`quality_control.result_type.${result_type}`}
35
+ />
36
+ </List.Description>
37
+ </List.Content>
38
+ </List.Item>
39
+ {resultCriteriaForType[result_type]}
40
+ </List>
41
+ ) : (
42
+ <FormattedMessage id="quality_control.result_criteria.empty" />
43
+ )}
44
+ </Segment>
45
+ </>
46
+ );
47
+ }
48
+
49
+ const ResultCriteriaPercentage = ({ resultCriteria }) => (
50
+ <>
51
+ <List.Item>
52
+ <Icon name="circle" color="green" />
53
+ <List.Content>
54
+ <List.Header>
55
+ <FormattedMessage id="quality.goal" />
56
+ </List.Header>
57
+ <List.Description>{resultCriteria.goal}%</List.Description>
58
+ </List.Content>
59
+ </List.Item>
60
+ <List.Item>
61
+ <Icon name="circle" color="yellow" />
62
+ <List.Content>
63
+ <List.Header>
64
+ <FormattedMessage id="quality.threshold" />
65
+ </List.Header>
66
+ <List.Description>{resultCriteria.minimum}%</List.Description>
67
+ </List.Content>
68
+ </List.Item>
69
+ </>
70
+ );
71
+
72
+ const ResultCriteriaDeviation = ({ resultCriteria }) => (
73
+ <>
74
+ <List.Item>
75
+ <Icon name="circle" color="green" />
76
+ <List.Content>
77
+ <List.Header>
78
+ <FormattedMessage id="quality.goal" />
79
+ </List.Header>
80
+ <List.Description>{resultCriteria.goal}%</List.Description>
81
+ </List.Content>
82
+ </List.Item>
83
+ <List.Item>
84
+ <Icon name="circle" color="yellow" />
85
+ <List.Content>
86
+ <List.Header>
87
+ <FormattedMessage id="quality.threshold" />
88
+ </List.Header>
89
+ <List.Description>{resultCriteria.maximum}%</List.Description>
90
+ </List.Content>
91
+ </List.Item>
92
+ </>
93
+ );
94
+
95
+ const ResultCriteriaErrorsNumber = ({ resultCriteria }) => (
96
+ <>
97
+ <List.Item>
98
+ <Icon name="circle" color="green" />
99
+ <List.Content>
100
+ <List.Header>
101
+ <FormattedMessage id="quality.goal" />
102
+ </List.Header>
103
+ <List.Description>{resultCriteria.goal}</List.Description>
104
+ </List.Content>
105
+ </List.Item>
106
+ <List.Item>
107
+ <Icon name="circle" color="yellow" />
108
+ <List.Content>
109
+ <List.Header>
110
+ <FormattedMessage id="quality.threshold" />
111
+ </List.Header>
112
+ <List.Description>{resultCriteria.maximum}</List.Description>
113
+ </List.Content>
114
+ </List.Item>
115
+ </>
116
+ );
117
+
118
+ ResultCriteria.propTypes = {
119
+ qualityControl: PropTypes.object,
120
+ };
@@ -0,0 +1,57 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useContext } from "react";
3
+ import { useIntl } from "react-intl";
4
+ import { Controller, useFormContext } from "react-hook-form";
5
+ import { Dropdown, Form, Segment } from "semantic-ui-react";
6
+ import QxContext from "@truedat/qx/components/QxContext";
7
+
8
+ import Deviation from "./resultCriterias/Deviation";
9
+ import ErrorsNumber from "./resultCriterias/ErrorsNumber";
10
+ import Percentage from "./resultCriterias/Percentage";
11
+
12
+ export default function ResultType() {
13
+ const { formatMessage } = useIntl();
14
+ const { control, watch } = useFormContext();
15
+ const context = useContext(QxContext);
16
+
17
+ const resultType = watch("result_type");
18
+ const resultTypeOptions = _.map((v) => ({
19
+ key: v,
20
+ value: v,
21
+ text: formatMessage({ id: `quality_control.result_type.${v}` }),
22
+ }))(["percentage", "deviation", "errors_number"]);
23
+
24
+ const resultCriteriaForType = {
25
+ deviation: <Deviation />,
26
+ errors_number: <ErrorsNumber />,
27
+ percentage: <Percentage />,
28
+ };
29
+
30
+ return (
31
+ <Segment>
32
+ <Controller
33
+ name={"result_type"}
34
+ control={control}
35
+ rules={{ required: true }}
36
+ render={({ field: { onBlur, onChange, value } }) => (
37
+ <Form.Field required>
38
+ <label>
39
+ {formatMessage({ id: "quality_control.result_type" })}
40
+ </label>
41
+ <Dropdown
42
+ selection
43
+ placeholder={formatMessage({ id: "quality_control.result_type" })}
44
+ onBlur={onBlur}
45
+ options={resultTypeOptions}
46
+ onChange={(_e, { value }) => onChange(value)}
47
+ value={value}
48
+ />
49
+ </Form.Field>
50
+ )}
51
+ />
52
+ <QxContext.Provider value={{ ...context, field: "result_criteria" }}>
53
+ {resultType ? resultCriteriaForType[resultType] : null}
54
+ </QxContext.Provider>
55
+ </Segment>
56
+ );
57
+ }
@@ -0,0 +1,89 @@
1
+ import React, { useContext } from "react";
2
+ import { useIntl } from "react-intl";
3
+ import { Controller, useFormContext } from "react-hook-form";
4
+ import { Form } from "semantic-ui-react";
5
+ import QxContext from "@truedat/qx/components/QxContext";
6
+ import { FieldLabel } from "@truedat/core/components";
7
+ import { numberRules } from "@truedat/core/services/formRules";
8
+
9
+ export default function Deviation() {
10
+ const { formatMessage } = useIntl();
11
+ const { field } = useContext(QxContext);
12
+ const { control, watch } = useFormContext();
13
+
14
+ const maximumField = `${field}.maximum`;
15
+ const goalField = `${field}.goal`;
16
+ const maximum = watch(maximumField);
17
+ const goal = watch(goalField);
18
+
19
+ return (
20
+ <>
21
+ <Controller
22
+ name={goalField}
23
+ control={control}
24
+ rules={numberRules({
25
+ formatMessage,
26
+ minValue: 0,
27
+ maxValue: maximum,
28
+ required: true,
29
+ })}
30
+ render={({
31
+ field: { onBlur, onChange, value },
32
+ fieldState: { error },
33
+ }) => (
34
+ <FieldLabel
35
+ label={formatMessage({
36
+ id: "quality_control.result_criteria.deviation.goal",
37
+ })}
38
+ required
39
+ error={error?.message}
40
+ >
41
+ <Form.Input
42
+ autoComplete="off"
43
+ placeholder={formatMessage({
44
+ id: "quality_control.result_criteria.deviation.goal",
45
+ })}
46
+ error={!!error}
47
+ onBlur={onBlur}
48
+ onChange={(_e, { value }) => onChange(value)}
49
+ value={value}
50
+ />
51
+ </FieldLabel>
52
+ )}
53
+ />
54
+ <Controller
55
+ name={maximumField}
56
+ control={control}
57
+ rules={numberRules({
58
+ formatMessage,
59
+ minValue: goal,
60
+ maxValue: 100,
61
+ required: true,
62
+ })}
63
+ render={({
64
+ field: { onBlur, onChange, value },
65
+ fieldState: { error },
66
+ }) => (
67
+ <FieldLabel
68
+ label={formatMessage({
69
+ id: "quality_control.result_criteria.deviation.maximum",
70
+ })}
71
+ required
72
+ error={error?.message}
73
+ >
74
+ <Form.Input
75
+ autoComplete="off"
76
+ placeholder={formatMessage({
77
+ id: "quality_control.result_criteria.deviation.maximum",
78
+ })}
79
+ error={!!error}
80
+ onBlur={onBlur}
81
+ onChange={(_e, { value }) => onChange(value)}
82
+ value={value}
83
+ />
84
+ </FieldLabel>
85
+ )}
86
+ />
87
+ </>
88
+ );
89
+ }