■ ■ ■ ■ ■ ■
opencti-platform/opencti-front/src/private/components/entities/cities/CityCreation.js
1 | | - | import React, { Component } from 'react'; |
2 | | - | import * as PropTypes from 'prop-types'; |
3 | | - | import { Formik, Form, Field } from 'formik'; |
4 | | - | import withStyles from '@mui/styles/withStyles'; |
5 | | - | import Drawer from '@mui/material/Drawer'; |
6 | | - | import Typography from '@mui/material/Typography'; |
7 | | - | import Button from '@mui/material/Button'; |
8 | | - | import IconButton from '@mui/material/IconButton'; |
9 | | - | import Fab from '@mui/material/Fab'; |
10 | | - | import { Add, Close } from '@mui/icons-material'; |
11 | | - | import * as Yup from 'yup'; |
12 | | - | import { graphql } from 'react-relay'; |
13 | | - | import { ConnectionHandler } from 'relay-runtime'; |
14 | | - | import * as R from 'ramda'; |
15 | | - | import inject18n from '../../../../components/i18n'; |
16 | | - | import { commitMutation } from '../../../../relay/environment'; |
17 | | - | import TextField from '../../../../components/TextField'; |
18 | | - | import CreatedByField from '../../common/form/CreatedByField'; |
19 | | - | import ObjectMarkingField from '../../common/form/ObjectMarkingField'; |
20 | | - | import MarkDownField from '../../../../components/MarkDownField'; |
21 | | - | import ExternalReferencesField from '../../common/form/ExternalReferencesField'; |
22 | | - | |
23 | | - | const styles = (theme) => ({ |
24 | | - | drawerPaper: { |
25 | | - | minHeight: '100vh', |
26 | | - | width: '50%', |
27 | | - | position: 'fixed', |
28 | | - | transition: theme.transitions.create('width', { |
29 | | - | easing: theme.transitions.easing.sharp, |
30 | | - | duration: theme.transitions.duration.enteringScreen, |
31 | | - | }), |
32 | | - | padding: 0, |
33 | | - | }, |
34 | | - | createButton: { |
35 | | - | position: 'fixed', |
36 | | - | bottom: 30, |
37 | | - | right: 30, |
38 | | - | }, |
39 | | - | buttons: { |
40 | | - | marginTop: 20, |
41 | | - | textAlign: 'right', |
42 | | - | }, |
43 | | - | button: { |
44 | | - | marginLeft: theme.spacing(2), |
45 | | - | }, |
46 | | - | header: { |
47 | | - | backgroundColor: theme.palette.background.nav, |
48 | | - | padding: '20px 20px 20px 60px', |
49 | | - | }, |
50 | | - | closeButton: { |
51 | | - | position: 'absolute', |
52 | | - | top: 12, |
53 | | - | left: 5, |
54 | | - | color: 'inherit', |
55 | | - | }, |
56 | | - | importButton: { |
57 | | - | position: 'absolute', |
58 | | - | top: 15, |
59 | | - | right: 20, |
60 | | - | }, |
61 | | - | container: { |
62 | | - | padding: '10px 20px 20px 20px', |
63 | | - | }, |
64 | | - | }); |
65 | | - | |
66 | | - | const cityMutation = graphql` |
67 | | - | mutation CityCreationMutation($input: CityAddInput!) { |
68 | | - | cityAdd(input: $input) { |
69 | | - | ...CityLine_node |
70 | | - | } |
71 | | - | } |
72 | | - | `; |
73 | | - | |
74 | | - | const cityValidation = (t) => Yup.object().shape({ |
75 | | - | name: Yup.string().required(t('This field is required')), |
76 | | - | description: Yup.string().nullable(), |
77 | | - | latitude: Yup.number() |
78 | | - | .typeError(t('This field must be a number')) |
79 | | - | .nullable(), |
80 | | - | longitude: Yup.number() |
81 | | - | .typeError(t('This field must be a number')) |
82 | | - | .nullable(), |
83 | | - | }); |
84 | | - | |
85 | | - | const sharedUpdater = (store, userId, paginationOptions, newEdge) => { |
86 | | - | const userProxy = store.get(userId); |
87 | | - | const conn = ConnectionHandler.getConnection( |
88 | | - | userProxy, |
89 | | - | 'Pagination_cities', |
90 | | - | paginationOptions, |
91 | | - | ); |
92 | | - | ConnectionHandler.insertEdgeBefore(conn, newEdge); |
93 | | - | }; |
94 | | - | |
95 | | - | class CityCreation extends Component { |
96 | | - | constructor(props) { |
97 | | - | super(props); |
98 | | - | this.state = { open: false }; |
99 | | - | } |
100 | | - | |
101 | | - | handleOpen() { |
102 | | - | this.setState({ open: true }); |
103 | | - | } |
104 | | - | |
105 | | - | handleClose() { |
106 | | - | this.setState({ open: false }); |
107 | | - | } |
108 | | - | |
109 | | - | onSubmit(values, { setSubmitting, resetForm }) { |
110 | | - | const finalValues = R.pipe( |
111 | | - | R.assoc('latitude', parseFloat(values.latitude)), |
112 | | - | R.assoc('longitude', parseFloat(values.longitude)), |
113 | | - | R.assoc('createdBy', values.createdBy?.value), |
114 | | - | R.assoc('objectMarking', R.pluck('value', values.objectMarking)), |
115 | | - | R.assoc('externalReferences', R.pluck('value', values.externalReferences)), |
116 | | - | )(values); |
117 | | - | commitMutation({ |
118 | | - | mutation: cityMutation, |
119 | | - | variables: { |
120 | | - | input: finalValues, |
121 | | - | }, |
122 | | - | updater: (store) => { |
123 | | - | const payload = store.getRootField('cityAdd'); |
124 | | - | const newEdge = payload.setLinkedRecord(payload, 'node'); // Creation of the pagination container. |
125 | | - | const container = store.getRoot(); |
126 | | - | sharedUpdater( |
127 | | - | store, |
128 | | - | container.getDataID(), |
129 | | - | this.props.paginationOptions, |
130 | | - | newEdge, |
131 | | - | ); |
132 | | - | }, |
133 | | - | setSubmitting, |
134 | | - | onCompleted: () => { |
135 | | - | setSubmitting(false); |
136 | | - | resetForm(); |
137 | | - | this.handleClose(); |
138 | | - | }, |
139 | | - | }); |
140 | | - | } |
141 | | - | |
142 | | - | onReset() { |
143 | | - | this.handleClose(); |
144 | | - | } |
145 | | - | |
146 | | - | render() { |
147 | | - | const { t, classes } = this.props; |
148 | | - | return ( |
149 | | - | <div> |
150 | | - | <Fab |
151 | | - | onClick={this.handleOpen.bind(this)} |
152 | | - | color="secondary" |
153 | | - | aria-label="Add" |
154 | | - | className={classes.createButton} |
155 | | - | > |
156 | | - | <Add /> |
157 | | - | </Fab> |
158 | | - | <Drawer |
159 | | - | open={this.state.open} |
160 | | - | anchor="right" |
161 | | - | elevation={1} |
162 | | - | sx={{ zIndex: 1202 }} |
163 | | - | classes={{ paper: classes.drawerPaper }} |
164 | | - | onClose={this.handleClose.bind(this)} |
165 | | - | > |
166 | | - | <div className={classes.header}> |
167 | | - | <IconButton |
168 | | - | aria-label="Close" |
169 | | - | className={classes.closeButton} |
170 | | - | onClick={this.handleClose.bind(this)} |
171 | | - | size="large" |
172 | | - | color="primary" |
173 | | - | > |
174 | | - | <Close fontSize="small" color="primary" /> |
175 | | - | </IconButton> |
176 | | - | <Typography variant="h6">{t('Create a city')}</Typography> |
177 | | - | </div> |
178 | | - | <div className={classes.container}> |
179 | | - | <Formik |
180 | | - | initialValues={{ |
181 | | - | name: '', |
182 | | - | description: '', |
183 | | - | latitude: '', |
184 | | - | longitude: '', |
185 | | - | createdBy: '', |
186 | | - | objectMarking: [], |
187 | | - | externalReferences: [], |
188 | | - | }} |
189 | | - | validationSchema={cityValidation(t)} |
190 | | - | onSubmit={this.onSubmit.bind(this)} |
191 | | - | onReset={this.onReset.bind(this)} |
192 | | - | > |
193 | | - | {({ |
194 | | - | submitForm, |
195 | | - | handleReset, |
196 | | - | isSubmitting, |
197 | | - | setFieldValue, |
198 | | - | values, |
199 | | - | }) => ( |
200 | | - | <Form style={{ margin: '20px 0 20px 0' }}> |
201 | | - | <Field |
202 | | - | component={TextField} |
203 | | - | variant="standard" |
204 | | - | name="name" |
205 | | - | label={t('Name')} |
206 | | - | fullWidth={true} |
207 | | - | detectDuplicate={['City']} |
208 | | - | /> |
209 | | - | <Field |
210 | | - | component={MarkDownField} |
211 | | - | name="description" |
212 | | - | label={t('Description')} |
213 | | - | fullWidth={true} |
214 | | - | multiline={true} |
215 | | - | rows={4} |
216 | | - | style={{ marginTop: 20 }} |
217 | | - | /> |
218 | | - | <Field |
219 | | - | component={TextField} |
220 | | - | variant="standard" |
221 | | - | name="latitude" |
222 | | - | label={t('Latitude')} |
223 | | - | fullWidth={true} |
224 | | - | style={{ marginTop: 20 }} |
225 | | - | /> |
226 | | - | <Field |
227 | | - | component={TextField} |
228 | | - | variant="standard" |
229 | | - | name="longitude" |
230 | | - | label={t('Longitude')} |
231 | | - | fullWidth={true} |
232 | | - | style={{ marginTop: 20 }} |
233 | | - | /> |
234 | | - | <CreatedByField |
235 | | - | name="createdBy" |
236 | | - | style={{ marginTop: 20, width: '100%' }} |
237 | | - | setFieldValue={setFieldValue} |
238 | | - | /> |
239 | | - | <ObjectMarkingField |
240 | | - | name="objectMarking" |
241 | | - | style={{ marginTop: 20, width: '100%' }} |
242 | | - | /> |
243 | | - | <ExternalReferencesField |
244 | | - | name="externalReferences" |
245 | | - | style={{ marginTop: 20, width: '100%' }} |
246 | | - | setFieldValue={setFieldValue} |
247 | | - | values={values.externalReferences} |
248 | | - | /> |
249 | | - | <div className={classes.buttons}> |
250 | | - | <Button |
251 | | - | variant="contained" |
252 | | - | onClick={handleReset} |
253 | | - | disabled={isSubmitting} |
254 | | - | classes={{ root: classes.button }} |
255 | | - | > |
256 | | - | {t('Cancel')} |
257 | | - | </Button> |
258 | | - | <Button |
259 | | - | variant="contained" |
260 | | - | color="secondary" |
261 | | - | onClick={submitForm} |
262 | | - | disabled={isSubmitting} |
263 | | - | classes={{ root: classes.button }} |
264 | | - | > |
265 | | - | {t('Create')} |
266 | | - | </Button> |
267 | | - | </div> |
268 | | - | </Form> |
269 | | - | )} |
270 | | - | </Formik> |
271 | | - | </div> |
272 | | - | </Drawer> |
273 | | - | </div> |
274 | | - | ); |
275 | | - | } |
276 | | - | } |
277 | | - | |
278 | | - | CityCreation.propTypes = { |
279 | | - | paginationOptions: PropTypes.object, |
280 | | - | classes: PropTypes.object, |
281 | | - | theme: PropTypes.object, |
282 | | - | t: PropTypes.func, |
283 | | - | }; |
284 | | - | |
285 | | - | export default R.compose( |
286 | | - | inject18n, |
287 | | - | withStyles(styles, { withTheme: true }), |
288 | | - | )(CityCreation); |
289 | | - | |