Skip to content

Commit c19641e

Browse files
committed
#2157: Refactor of the create dataset page and attribute table validation
1 parent a1bfa57 commit c19641e

30 files changed

+1813
-26
lines changed

geonode_mapstore_client/client/js/apps/gn-catalogue.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ const getViewer = (component) => {
104104
const viewers = {
105105
[appRouteComponentTypes.VIEWER]: ViewerRoute,
106106
[appRouteComponentTypes.CATALOGUE]: useRedirect ? RedirectRoute : ComponentsRoute,
107-
[appRouteComponentTypes.DATASET_UPLOAD]: ComponentsRoute,
108-
[appRouteComponentTypes.DOCUMENT_UPLOAD]: ComponentsRoute,
107+
[appRouteComponentTypes.COMPONENTS]: ComponentsRoute,
109108
[appRouteComponentTypes.MAP_VIEWER]: MapViewerRoute
110109
};
111110
return viewers[component];
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright 2025, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
import React from 'react';
9+
import PropTypes from 'prop-types';
10+
import { FormGroup, FormControl, Checkbox, HelpBlock } from 'react-bootstrap';
11+
12+
import FlexBox from '@mapstore/framework/components/layout/FlexBox';
13+
import Message from '@mapstore/framework/components/I18N/Message';
14+
import { getMessageById } from '@mapstore/framework/utils/LocaleUtils';
15+
16+
import { AttributeTypes, getAttributeControlId, RestrictionsTypes } from '../utils/CreateDatasetUtils';
17+
import RangeRestriction from './RangeRestriction';
18+
import EnumRestriction from './EnumRestriction';
19+
20+
const CreateDatasetAttributeRow = ({
21+
data,
22+
geometryAttribute,
23+
tools,
24+
onChange,
25+
getErrorByPath = () => undefined,
26+
index
27+
}, context) => {
28+
29+
const errors = {
30+
name: getErrorByPath(`/attributes/${index}/name`),
31+
restrictionsType: getErrorByPath(`/attributes/${index}/restrictionsType`),
32+
restrictionsRangeMin: getErrorByPath(`/attributes/${index}/restrictionsRangeMin`),
33+
restrictionsRangeMax: getErrorByPath(`/attributes/${index}/restrictionsRangeMax`)
34+
};
35+
36+
const typesOptions = geometryAttribute
37+
? [
38+
{ value: AttributeTypes.Point, labelId: 'gnviewer.points' },
39+
{ value: AttributeTypes.LineString, labelId: 'gnviewer.lines' },
40+
{ value: AttributeTypes.Polygon, labelId: 'gnviewer.polygons' }]
41+
: [
42+
{ value: AttributeTypes.String, labelId: 'gnviewer.string' },
43+
{ value: AttributeTypes.Integer, labelId: 'gnviewer.integer' },
44+
{ value: AttributeTypes.Float, labelId: 'gnviewer.float' },
45+
{ value: AttributeTypes.Date, labelId: 'gnviewer.date' }
46+
];
47+
48+
const restrictionsOptions = [
49+
AttributeTypes.Integer,
50+
AttributeTypes.Float,
51+
AttributeTypes.String
52+
].includes(data?.type)
53+
? [
54+
{ value: RestrictionsTypes.None, labelId: 'gnviewer.none' },
55+
...(data?.type !== AttributeTypes.String
56+
? [{ value: RestrictionsTypes.Range, labelId: 'gnviewer.range' }]
57+
: []
58+
),
59+
{ value: RestrictionsTypes.Options, labelId: 'gnviewer.options' }
60+
]
61+
: [{ value: RestrictionsTypes.None, labelId: 'gnviewer.none' }];
62+
63+
function handleOnChange(properties) {
64+
onChange({
65+
...data,
66+
...properties
67+
});
68+
}
69+
70+
function handleTypeChange(event) {
71+
const newType = event.target.value;
72+
const currentType = data?.type;
73+
74+
// If type is changing, clear restrictions and set to default
75+
if (newType !== currentType) {
76+
onChange({
77+
...data,
78+
type: newType,
79+
restrictionsType: RestrictionsTypes.None,
80+
restrictionsRangeMin: null,
81+
restrictionsRangeMax: null,
82+
restrictionsOptions: []
83+
});
84+
} else {
85+
handleOnChange({ type: newType });
86+
}
87+
}
88+
89+
return (
90+
<tr className="gn-dataset-attribute">
91+
<td className="gn-attribute-name">
92+
<FormGroup
93+
controlId={getAttributeControlId(data, 'name')}
94+
validationState={errors?.name ? 'error' : undefined}
95+
>
96+
<FormControl
97+
type="text"
98+
value={data?.name || ''}
99+
disabled={!!geometryAttribute}
100+
onChange={(event) => handleOnChange({ name: event.target.value })}
101+
/>
102+
{errors?.name ? <HelpBlock><Message msgId={errors.name} /></HelpBlock> : null}
103+
</FormGroup>
104+
</td>
105+
<td className="gn-attribute-type">
106+
<FormGroup controlId={getAttributeControlId(data, 'type')}>
107+
<FormControl
108+
value={data?.type || ''}
109+
componentClass="select"
110+
placeholder="select"
111+
onChange={handleTypeChange}
112+
>
113+
{typesOptions.map(({ labelId, value }) =>
114+
<option key={value} value={value}>
115+
{getMessageById(context.messages, labelId)}
116+
</option>
117+
)}
118+
</FormControl>
119+
</FormGroup>
120+
</td>
121+
<td className="gn-attribute-nillable">
122+
<FormGroup controlId={getAttributeControlId(data, 'nillable')}>
123+
<Checkbox
124+
style={{ paddingTop: 6 }}
125+
checked={!!data?.nillable}
126+
disabled={!!geometryAttribute}
127+
onChange={(event) =>
128+
handleOnChange({ nillable: event.target.checked })
129+
}
130+
/>
131+
</FormGroup>
132+
</td>
133+
<td>
134+
<FlexBox column gap="sm">
135+
<FormGroup
136+
controlId={getAttributeControlId(data, 'restrictions')}
137+
validationState={errors?.restrictionsType ? 'error' : undefined}>
138+
<FormControl
139+
value={data?.restrictionsType}
140+
componentClass="select"
141+
placeholder="select"
142+
disabled={!!geometryAttribute}
143+
onChange={(event) =>
144+
handleOnChange({ restrictionsType: event.target.value })
145+
}
146+
>
147+
{restrictionsOptions.map(({ labelId, value }) =>
148+
<option key={value} value={value}>
149+
{getMessageById(context.messages, labelId)}
150+
</option>
151+
)}
152+
</FormControl>
153+
{errors?.restrictionsType ? <HelpBlock>{errors.restrictionsType}</HelpBlock> : null}
154+
</FormGroup>
155+
{data?.restrictionsType === RestrictionsTypes.Range ?
156+
<RangeRestriction
157+
errors={errors}
158+
data={data}
159+
handleOnChange={handleOnChange}
160+
/> : null}
161+
{data?.restrictionsType === RestrictionsTypes.Options
162+
? <EnumRestriction
163+
index={index}
164+
data={data}
165+
handleOnChange={handleOnChange}
166+
getErrorByPath={getErrorByPath}
167+
/> : null}
168+
</FlexBox>
169+
</td>
170+
<td className="gn-attribute-tools">
171+
{tools}
172+
</td>
173+
</tr>
174+
);
175+
};
176+
177+
CreateDatasetAttributeRow.contextTypes = {
178+
messages: PropTypes.object
179+
};
180+
181+
export default CreateDatasetAttributeRow;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2025, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
import React from "react";
9+
import { v4 as uuid } from 'uuid';
10+
import { FormGroup, FormControl, HelpBlock, Glyphicon } from "react-bootstrap";
11+
12+
import FlexBox from "@mapstore/framework/components/layout/FlexBox";
13+
import Message from "@mapstore/framework/components/I18N/Message";
14+
import Button from "@mapstore/framework/components/layout/Button";
15+
import Text from "@mapstore/framework/components/layout/Text";
16+
17+
import { AttributeTypes, getAttributeControlId, parseNumber } from "../utils/CreateDatasetUtils";
18+
19+
const EnumRestriction = ({
20+
index,
21+
data = {},
22+
handleOnChange = () => {},
23+
getErrorByPath = () => {}
24+
}) => {
25+
return (
26+
<FlexBox column wrap gap="sm">
27+
<FlexBox gap="sm" column component="ul">
28+
{(data?.restrictionsOptions || []).map((option, idx) => {
29+
const optionsError = {
30+
value: getErrorByPath(`/attributes/${index}/restrictionsOptions/${idx}/value`)
31+
};
32+
return (
33+
<FlexBox component="li" gap="sm" key={option.id}>
34+
<Text style={{ paddingTop: 6 }}></Text>
35+
<FlexBox.Fill
36+
component={FormGroup}
37+
key={option.id}
38+
validationState={optionsError?.value ? 'error' : undefined}
39+
controlId={getAttributeControlId(data, `option-${option.id}`)}
40+
>
41+
<FormControl
42+
type={data?.type === AttributeTypes.String ? "text" : "number"}
43+
value={option.value}
44+
onChange={(event) => handleOnChange({
45+
restrictionsOptions: (data?.restrictionsOptions || [])
46+
.map((opt) => {
47+
return opt.id !== option.id
48+
? opt : {
49+
...option,
50+
value: data?.type === AttributeTypes.String
51+
? event.target.value
52+
: parseNumber(event.target.value)
53+
};
54+
})
55+
})}
56+
/>
57+
{optionsError?.value ? <HelpBlock>
58+
<Message msgId={optionsError.value} />
59+
</HelpBlock> : null}
60+
</FlexBox.Fill>
61+
<Button square
62+
onClick={() => handleOnChange({
63+
restrictionsOptions: (data?.restrictionsOptions || [])
64+
.filter(opt => opt.id !== option.id)
65+
})}>
66+
<Glyphicon glyph="trash" />
67+
</Button>
68+
</FlexBox>
69+
);
70+
})}
71+
<div>
72+
<Button size="sm" onClick={() => handleOnChange({
73+
restrictionsOptions: [
74+
...(data?.restrictionsOptions || []),
75+
{
76+
id: uuid(),
77+
value: ''
78+
}
79+
]
80+
})}>
81+
<Glyphicon glyph="plus" />
82+
<Message msgId="gnviewer.addOption" />
83+
</Button>
84+
</div>
85+
</FlexBox>
86+
</FlexBox>
87+
);
88+
};
89+
90+
export default EnumRestriction;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2025, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
import React from "react";
9+
import { FormGroup, FormControl, ControlLabel, HelpBlock } from "react-bootstrap";
10+
11+
import FlexBox from "@mapstore/framework/components/layout/FlexBox";
12+
import Message from "@mapstore/framework/components/I18N/Message";
13+
14+
import { getAttributeControlId, parseNumber } from "../utils/CreateDatasetUtils";
15+
16+
const RangeRestriction = ({
17+
errors = {},
18+
data = {},
19+
handleOnChange = () => {}
20+
}) => {
21+
return (
22+
<FlexBox centerChildrenVertically wrap gap="sm">
23+
<FlexBox.Fill
24+
flexBox
25+
component={FormGroup}
26+
validationState={errors?.restrictionsRangeMin ? 'error' : undefined}
27+
controlId={getAttributeControlId(data, 'restrictions-min')}
28+
centerChildrenVertically
29+
gap="sm"
30+
>
31+
<ControlLabel><Message msgId="gnviewer.min" /></ControlLabel>
32+
<FormControl
33+
type="number"
34+
value={data?.restrictionsRangeMin}
35+
onChange={(event) => handleOnChange({
36+
restrictionsRangeMin: parseNumber(event.target.value)
37+
})}
38+
/>
39+
{errors?.restrictionsRangeMin ?
40+
<HelpBlock>
41+
<Message msgId={errors.restrictionsRangeMin} />
42+
</HelpBlock> : null}
43+
</FlexBox.Fill>
44+
<FlexBox.Fill
45+
flexBox
46+
component={FormGroup}
47+
validationState={errors?.restrictionsRangeMax ? 'error' : undefined}
48+
controlId={getAttributeControlId(data, 'restrictions-max')}
49+
gap="sm"
50+
centerChildrenVertically
51+
>
52+
<ControlLabel><Message msgId="gnviewer.max" /></ControlLabel>
53+
<FormControl
54+
type="number"
55+
value={data?.restrictionsRangeMax}
56+
onChange={(event) => handleOnChange({
57+
restrictionsRangeMax: parseNumber(event.target.value)
58+
})}
59+
/>
60+
{errors?.restrictionsRangeMax
61+
? <HelpBlock>
62+
<Message msgId={errors.restrictionsRangeMax} />
63+
</HelpBlock> : null}
64+
</FlexBox.Fill>
65+
</FlexBox>
66+
);
67+
};
68+
69+
export default RangeRestriction;

0 commit comments

Comments
 (0)