mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
				synced 2025-10-31 10:47:54 +00:00 
			
		
		
		
	Version 0.9.71
This commit is contained in:
		
							
								
								
									
										82
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										82
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-libs", |   "name": "ucentral-libs", | ||||||
|   "version": "0.9.51", |   "version": "0.9.71", | ||||||
|   "lockfileVersion": 2, |   "lockfileVersion": 2, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "ucentral-libs", |       "name": "ucentral-libs", | ||||||
|       "version": "0.9.51", |       "version": "0.9.71", | ||||||
|       "license": "BSD-3-Clause", |       "license": "BSD-3-Clause", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@coreui/coreui": "^3.4.0", |         "@coreui/coreui": "^3.4.0", | ||||||
| @@ -14,9 +14,11 @@ | |||||||
|         "@coreui/icons-react": "^1.1.0", |         "@coreui/icons-react": "^1.1.0", | ||||||
|         "@coreui/react": "^3.4.6", |         "@coreui/react": "^3.4.6", | ||||||
|         "@coreui/react-chartjs": "^1.1.0", |         "@coreui/react-chartjs": "^1.1.0", | ||||||
|  |         "libphonenumber-js": "^1.9.37", | ||||||
|         "lodash": "^4.17.21", |         "lodash": "^4.17.21", | ||||||
|         "react-flow-renderer": "^9.6.6", |         "react-flow-renderer": "^9.6.6", | ||||||
|         "react-paginate": "^7.1.3", |         "react-paginate": "^7.1.3", | ||||||
|  |         "react-phone-input-2": "^2.14.0", | ||||||
|         "react-router-dom": "^5.2.0", |         "react-router-dom": "^5.2.0", | ||||||
|         "react-select": "^4.3.1", |         "react-select": "^4.3.1", | ||||||
|         "react-tooltip": "^4.2.21", |         "react-tooltip": "^4.2.21", | ||||||
| @@ -8278,6 +8280,11 @@ | |||||||
|         "node": ">= 0.8.0" |         "node": ">= 0.8.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/libphonenumber-js": { | ||||||
|  |       "version": "1.9.37", | ||||||
|  |       "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz", | ||||||
|  |       "integrity": "sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg==" | ||||||
|  |     }, | ||||||
|     "node_modules/lines-and-columns": { |     "node_modules/lines-and-columns": { | ||||||
|       "version": "1.1.6", |       "version": "1.1.6", | ||||||
|       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", |       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", | ||||||
| @@ -8518,8 +8525,12 @@ | |||||||
|     "node_modules/lodash.debounce": { |     "node_modules/lodash.debounce": { | ||||||
|       "version": "4.0.8", |       "version": "4.0.8", | ||||||
|       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", |       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", | ||||||
|       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", |       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" | ||||||
|       "dev": true |     }, | ||||||
|  |     "node_modules/lodash.memoize": { | ||||||
|  |       "version": "4.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", | ||||||
|  |       "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" | ||||||
|     }, |     }, | ||||||
|     "node_modules/lodash.merge": { |     "node_modules/lodash.merge": { | ||||||
|       "version": "4.6.2", |       "version": "4.6.2", | ||||||
| @@ -8527,6 +8538,16 @@ | |||||||
|       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", |       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/lodash.reduce": { | ||||||
|  |       "version": "4.6.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", | ||||||
|  |       "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" | ||||||
|  |     }, | ||||||
|  |     "node_modules/lodash.startswith": { | ||||||
|  |       "version": "4.2.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", | ||||||
|  |       "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" | ||||||
|  |     }, | ||||||
|     "node_modules/lodash.truncate": { |     "node_modules/lodash.truncate": { | ||||||
|       "version": "4.4.2", |       "version": "4.4.2", | ||||||
|       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", |       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", | ||||||
| @@ -10482,6 +10503,23 @@ | |||||||
|         "react": "^16.0.0 || ^17.0.0" |         "react": "^16.0.0 || ^17.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/react-phone-input-2": { | ||||||
|  |       "version": "2.14.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", | ||||||
|  |       "integrity": "sha512-gOY3jUpwO7ulryXPEdqzH7L6DPqI9RQxKfBxZbgqAwXyALGsmwLWFyi2RQwXlBLWN/EPPT4Nv6I9TESVY2YBcg==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "classnames": "^2.2.6", | ||||||
|  |         "lodash.debounce": "^4.0.8", | ||||||
|  |         "lodash.memoize": "^4.1.2", | ||||||
|  |         "lodash.reduce": "^4.6.0", | ||||||
|  |         "lodash.startswith": "^4.2.1", | ||||||
|  |         "prop-types": "^15.7.2" | ||||||
|  |       }, | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "react": "^16.12.0 || ^17.0.0", | ||||||
|  |         "react-dom": "^16.12.0 || ^17.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/react-redux": { |     "node_modules/react-redux": { | ||||||
|       "version": "7.2.4", |       "version": "7.2.4", | ||||||
|       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", |       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", | ||||||
| @@ -20369,6 +20407,11 @@ | |||||||
|         "type-check": "~0.4.0" |         "type-check": "~0.4.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "libphonenumber-js": { | ||||||
|  |       "version": "1.9.37", | ||||||
|  |       "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz", | ||||||
|  |       "integrity": "sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg==" | ||||||
|  |     }, | ||||||
|     "lines-and-columns": { |     "lines-and-columns": { | ||||||
|       "version": "1.1.6", |       "version": "1.1.6", | ||||||
|       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", |       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", | ||||||
| @@ -20557,8 +20600,12 @@ | |||||||
|     "lodash.debounce": { |     "lodash.debounce": { | ||||||
|       "version": "4.0.8", |       "version": "4.0.8", | ||||||
|       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", |       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", | ||||||
|       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", |       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" | ||||||
|       "dev": true |     }, | ||||||
|  |     "lodash.memoize": { | ||||||
|  |       "version": "4.1.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", | ||||||
|  |       "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" | ||||||
|     }, |     }, | ||||||
|     "lodash.merge": { |     "lodash.merge": { | ||||||
|       "version": "4.6.2", |       "version": "4.6.2", | ||||||
| @@ -20566,6 +20613,16 @@ | |||||||
|       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", |       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "lodash.reduce": { | ||||||
|  |       "version": "4.6.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", | ||||||
|  |       "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" | ||||||
|  |     }, | ||||||
|  |     "lodash.startswith": { | ||||||
|  |       "version": "4.2.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", | ||||||
|  |       "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" | ||||||
|  |     }, | ||||||
|     "lodash.truncate": { |     "lodash.truncate": { | ||||||
|       "version": "4.4.2", |       "version": "4.4.2", | ||||||
|       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", |       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", | ||||||
| @@ -22049,6 +22106,19 @@ | |||||||
|         "prop-types": "^15.6.1" |         "prop-types": "^15.6.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "react-phone-input-2": { | ||||||
|  |       "version": "2.14.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.14.0.tgz", | ||||||
|  |       "integrity": "sha512-gOY3jUpwO7ulryXPEdqzH7L6DPqI9RQxKfBxZbgqAwXyALGsmwLWFyi2RQwXlBLWN/EPPT4Nv6I9TESVY2YBcg==", | ||||||
|  |       "requires": { | ||||||
|  |         "classnames": "^2.2.6", | ||||||
|  |         "lodash.debounce": "^4.0.8", | ||||||
|  |         "lodash.memoize": "^4.1.2", | ||||||
|  |         "lodash.reduce": "^4.6.0", | ||||||
|  |         "lodash.startswith": "^4.2.1", | ||||||
|  |         "prop-types": "^15.7.2" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "react-redux": { |     "react-redux": { | ||||||
|       "version": "7.2.4", |       "version": "7.2.4", | ||||||
|       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", |       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-libs", |   "name": "ucentral-libs", | ||||||
|   "version": "0.9.51", |   "version": "0.9.71", | ||||||
|   "main": "dist/index.js", |   "main": "dist/index.js", | ||||||
|   "source": "src/index.js", |   "source": "src/index.js", | ||||||
|   "engines": { |   "engines": { | ||||||
| @@ -9,20 +9,22 @@ | |||||||
|   "files": [ |   "files": [ | ||||||
|     "dist" |     "dist" | ||||||
|   ], |   ], | ||||||
|   "license" : "BSD-3-Clause", |   "license": "BSD-3-Clause", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@coreui/coreui": "^3.4.0", |     "@coreui/coreui": "^3.4.0", | ||||||
|     "@coreui/icons": "^2.0.1", |     "@coreui/icons": "^2.0.1", | ||||||
|     "@coreui/icons-react": "^1.1.0", |     "@coreui/icons-react": "^1.1.0", | ||||||
|     "@coreui/react": "^3.4.6", |     "@coreui/react": "^3.4.6", | ||||||
|     "@coreui/react-chartjs": "^1.1.0", |     "@coreui/react-chartjs": "^1.1.0", | ||||||
|     "react-tooltip": "^4.2.21", |     "libphonenumber-js": "^1.9.37", | ||||||
|  |     "lodash": "^4.17.21", | ||||||
|     "react-flow-renderer": "^9.6.6", |     "react-flow-renderer": "^9.6.6", | ||||||
|     "react-paginate": "^7.1.3", |     "react-paginate": "^7.1.3", | ||||||
|  |     "react-phone-input-2": "^2.14.0", | ||||||
|     "react-router-dom": "^5.2.0", |     "react-router-dom": "^5.2.0", | ||||||
|     "react-select": "^4.3.1", |     "react-select": "^4.3.1", | ||||||
|     "uuid": "^8.3.2", |     "react-tooltip": "^4.2.21", | ||||||
|     "lodash": "^4.17.21" |     "uuid": "^8.3.2" | ||||||
|   }, |   }, | ||||||
|   "peerDependencies": { |   "peerDependencies": { | ||||||
|     "prop-types": "^15.7.2", |     "prop-types": "^15.7.2", | ||||||
|   | |||||||
							
								
								
									
										353
									
								
								src/components/AddContactForm/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								src/components/AddContactForm/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,353 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import Select from 'react-select'; | ||||||
|  | import CreatableSelect from 'react-select/creatable'; | ||||||
|  | import { | ||||||
|  |   CForm, | ||||||
|  |   CInput, | ||||||
|  |   CLabel, | ||||||
|  |   CCol, | ||||||
|  |   CFormGroup, | ||||||
|  |   CInvalidFeedback, | ||||||
|  |   CFormText, | ||||||
|  |   CRow, | ||||||
|  |   CDataTable, | ||||||
|  |   CLink, | ||||||
|  |   CPopover, | ||||||
|  |   CButton, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { cilPlus } from '@coreui/icons'; | ||||||
|  | import FormattedDate from '../FormattedDate'; | ||||||
|  |  | ||||||
|  | const AddContactForm = ({ t, disable, fields, updateField, updateFieldWithKey, entities }) => { | ||||||
|  |   const [filter, setFilter] = useState(''); | ||||||
|  |   const [selectedEntity, setSelectedEntity] = useState(''); | ||||||
|  |  | ||||||
|  |   const onPhonesChange = (v) => updateFieldWithKey('phones', { value: v.map((obj) => obj.value) }); | ||||||
|  |   const onMobilesChange = (v) => | ||||||
|  |     updateFieldWithKey('mobiles', { value: v.map((obj) => obj.value) }); | ||||||
|  |  | ||||||
|  |   const NoOptionsMessage = () => ( | ||||||
|  |     <h6 className="text-center pt-2">{t('common.type_for_options')}</h6> | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const columns = [ | ||||||
|  |     { key: 'created', label: t('common.created'), _style: { width: '20%' }, filter: false }, | ||||||
|  |     { key: 'name', label: t('user.name'), _style: { width: '25%' }, filter: false }, | ||||||
|  |     { key: 'description', label: t('user.description'), _style: { width: '50%' } }, | ||||||
|  |     { key: 'actions', label: '', _style: { width: '15%' }, filter: false }, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   const selectEntity = ({ id, name }) => { | ||||||
|  |     updateFieldWithKey('entity', { value: id }); | ||||||
|  |     setSelectedEntity(name); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CForm> | ||||||
|  |       <CRow> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="type"> | ||||||
|  |           {t('contact.type')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <div style={{ width: '200px' }}> | ||||||
|  |             <Select | ||||||
|  |               id="type" | ||||||
|  |               value={ | ||||||
|  |                 fields.type.value !== '' | ||||||
|  |                   ? { value: fields.type.value, label: fields.type.value } | ||||||
|  |                   : null | ||||||
|  |               } | ||||||
|  |               onChange={(v) => updateFieldWithKey('type', { value: v.value })} | ||||||
|  |               options={[ | ||||||
|  |                 { label: 'SUBSCRIBER', value: 'SUBSCRIBER' }, | ||||||
|  |                 { label: 'USER', value: 'USER' }, | ||||||
|  |                 { label: 'INSTALLER', value: 'INSTALLER' }, | ||||||
|  |                 { label: 'CSR', value: 'CSR' }, | ||||||
|  |                 { label: 'MANAGER', value: 'MANAGER' }, | ||||||
|  |                 { label: 'BUSINESSOWNER', value: 'BUSINESSOWNER' }, | ||||||
|  |                 { label: 'TECHNICIAN', value: 'TECHNICIAN' }, | ||||||
|  |                 { label: 'CORPORATE', value: 'CORPORATE' }, | ||||||
|  |               ]} | ||||||
|  |               isDisabled={disable} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |           <CFormText color={fields.type.error ? 'danger' : ''}>{t('common.required')}</CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="title"> | ||||||
|  |           {t('contact.user_title')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="title" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.title.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.title.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="salutation"> | ||||||
|  |           {t('contact.salutation')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <div style={{ width: '120px' }}> | ||||||
|  |             <Select | ||||||
|  |               id="salutation" | ||||||
|  |               value={{ | ||||||
|  |                 value: fields.salutation.value, | ||||||
|  |                 label: fields.salutation.value === '' ? 'None' : fields.salutation.value, | ||||||
|  |               }} | ||||||
|  |               onChange={(v) => updateFieldWithKey('salutation', { value: v.value })} | ||||||
|  |               options={[ | ||||||
|  |                 { label: 'None', value: '' }, | ||||||
|  |                 { label: 'Mr.', value: 'Mr.' }, | ||||||
|  |                 { label: 'Ms.', value: 'Ms.' }, | ||||||
|  |                 { label: 'Mx.', value: 'Mx.' }, | ||||||
|  |                 { label: 'Dr.', value: 'Dr.' }, | ||||||
|  |               ]} | ||||||
|  |               isDisabled={disable} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="accessPIN"> | ||||||
|  |           {t('contact.access_pin')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="accessPIN" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.accessPIN.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.accessPIN.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CInvalidFeedback>{t('common.required')}</CInvalidFeedback> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="firstname"> | ||||||
|  |           {t('contact.first_name')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="firstname" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.firstname.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.firstname.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CFormText color={fields.firstname.error ? 'danger' : ''}> | ||||||
|  |             {t('common.required')} | ||||||
|  |           </CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="lastname"> | ||||||
|  |           {t('contact.last_name')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="lastname" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.lastname.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.lastname.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CFormText color={fields.lastname.error ? 'danger' : ''}> | ||||||
|  |             {t('common.required')} | ||||||
|  |           </CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="initials"> | ||||||
|  |           {t('contact.initials')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="initials" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.initials.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel sm="2" col htmlFor="visual"> | ||||||
|  |           {t('contact.visual')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="visual" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.visual.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="primaryEmail"> | ||||||
|  |           {t('contact.primary_email')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="primaryEmail" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.primaryEmail.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.primaryEmail.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CFormText color={fields.primaryEmail.error ? 'danger' : ''}> | ||||||
|  |             {t('common.required')} | ||||||
|  |           </CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="secondaryEmail"> | ||||||
|  |           {t('contact.secondary_email')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="secondaryEmail" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.secondaryEmail.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.secondaryEmail.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CInvalidFeedback>{t('common.required')}</CInvalidFeedback> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="phones"> | ||||||
|  |           Landlines | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CreatableSelect | ||||||
|  |             isMulti | ||||||
|  |             id="phones" | ||||||
|  |             isDisabled={disable} | ||||||
|  |             onChange={onPhonesChange} | ||||||
|  |             components={{ NoOptionsMessage }} | ||||||
|  |             options={[]} | ||||||
|  |             value={fields.phones.value.map((opt) => ({ value: opt, label: opt }))} | ||||||
|  |             placeholder={t('common.type_for_options')} | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="phones"> | ||||||
|  |           Mobiles | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CreatableSelect | ||||||
|  |             id="mobiles" | ||||||
|  |             isMulti | ||||||
|  |             isDisabled={disable} | ||||||
|  |             onChange={onMobilesChange} | ||||||
|  |             components={{ NoOptionsMessage }} | ||||||
|  |             options={[]} | ||||||
|  |             value={fields.mobiles.value.map((opt) => ({ value: opt, label: opt }))} | ||||||
|  |             placeholder={t('common.type_for_options')} | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel sm="2" col htmlFor="description"> | ||||||
|  |           {t('user.description')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="description" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.description.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel sm="2" col htmlFor="initialNote"> | ||||||
|  |           {t('user.note')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="initialNote" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.initialNote.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |       </CRow> | ||||||
|  |       <CFormGroup row className="pt-2 pb-1"> | ||||||
|  |         <CLabel sm="2" col htmlFor="title"> | ||||||
|  |           {t('entity.selected_entity')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4" className="pt-2"> | ||||||
|  |           <h6>{fields.entity.value === '' ? t('entity.need_select_entity') : selectedEntity}</h6> | ||||||
|  |         </CCol> | ||||||
|  |       </CFormGroup> | ||||||
|  |       <div className="overflow-auto border mb-1" style={{ height: '200px' }}> | ||||||
|  |         <CInput | ||||||
|  |           className="w-50 mb-2" | ||||||
|  |           type="text" | ||||||
|  |           placeholder="Search" | ||||||
|  |           value={filter} | ||||||
|  |           onChange={(e) => setFilter(e.target.value)} | ||||||
|  |         /> | ||||||
|  |         <CDataTable | ||||||
|  |           items={entities} | ||||||
|  |           fields={columns} | ||||||
|  |           hover | ||||||
|  |           tableFilterValue={filter} | ||||||
|  |           border | ||||||
|  |           scopedSlots={{ | ||||||
|  |             name: (item) => ( | ||||||
|  |               <td> | ||||||
|  |                 <CLink | ||||||
|  |                   className="c-subheader-nav-link" | ||||||
|  |                   aria-current="page" | ||||||
|  |                   to={() => `/configuration/${item.id}`} | ||||||
|  |                 > | ||||||
|  |                   {item.name} | ||||||
|  |                 </CLink> | ||||||
|  |               </td> | ||||||
|  |             ), | ||||||
|  |             created: (item) => ( | ||||||
|  |               <td> | ||||||
|  |                 <FormattedDate date={item.created} /> | ||||||
|  |               </td> | ||||||
|  |             ), | ||||||
|  |             actions: (item) => ( | ||||||
|  |               <td> | ||||||
|  |                 <CPopover content={t('entity.select_entity')}> | ||||||
|  |                   <CButton color="primary" variant="outline" onClick={() => selectEntity(item)}> | ||||||
|  |                     <CIcon content={cilPlus} /> | ||||||
|  |                   </CButton> | ||||||
|  |                 </CPopover> | ||||||
|  |               </td> | ||||||
|  |             ), | ||||||
|  |           }} | ||||||
|  |         /> | ||||||
|  |       </div> | ||||||
|  |     </CForm> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | AddContactForm.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   disable: PropTypes.bool.isRequired, | ||||||
|  |   fields: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   updateField: PropTypes.func.isRequired, | ||||||
|  |   updateFieldWithKey: PropTypes.func.isRequired, | ||||||
|  |   entities: PropTypes.instanceOf(Array).isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default AddContactForm; | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import React from 'react'; | import React, { useEffect } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { | import { | ||||||
|   CFormGroup, |   CFormGroup, | ||||||
| @@ -13,7 +13,7 @@ import { | |||||||
|   CDataTable, |   CDataTable, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { cilMinus, cilSave, cilX } from '@coreui/icons'; | import { cilMinus, cilPlus, cilSave, cilX } from '@coreui/icons'; | ||||||
| import useToggle from '../../../hooks/useToggle'; | import useToggle from '../../../hooks/useToggle'; | ||||||
|  |  | ||||||
| const CustomMultiModal = ({ | const CustomMultiModal = ({ | ||||||
| @@ -28,15 +28,17 @@ const CustomMultiModal = ({ | |||||||
|   secondCol, |   secondCol, | ||||||
|   length, |   length, | ||||||
|   modalSize, |   modalSize, | ||||||
|  |   itemName, | ||||||
|  |   noTable, | ||||||
|  |   toggleAdd, | ||||||
|  |   reset, | ||||||
| }) => { | }) => { | ||||||
|   const [show, toggle] = useToggle(); |   const [show, toggle] = useToggle(); | ||||||
|  |  | ||||||
|   const getLabel = () => { |   const getLabel = () => { | ||||||
|     if (length === 0) return t('common.add_items'); |     if (length === 0) return `Manage ${itemName}`; | ||||||
|  |  | ||||||
|     if (length === 1) return `${length} ${t('common.item')}`; |     return `Manage ${itemName} (${length})`; | ||||||
|  |  | ||||||
|     return `${length} ${t('common.items')}`; |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const remove = (v) => { |   const remove = (v) => { | ||||||
| @@ -55,6 +57,10 @@ const CustomMultiModal = ({ | |||||||
|     toggle(); |     toggle(); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (show && reset !== null) reset(); | ||||||
|  |   }, [show]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <CFormGroup row className="py-1"> |     <CFormGroup row className="py-1"> | ||||||
|       <CLabel col sm={firstCol} htmlFor="name"> |       <CLabel col sm={firstCol} htmlFor="name"> | ||||||
| @@ -69,6 +75,17 @@ const CustomMultiModal = ({ | |||||||
|         <CModalHeader className="p-1"> |         <CModalHeader className="p-1"> | ||||||
|           <CModalTitle className="pl-1 pt-1">{label}</CModalTitle> |           <CModalTitle className="pl-1 pt-1">{label}</CModalTitle> | ||||||
|           <div className="text-right"> |           <div className="text-right"> | ||||||
|  |             <CPopover content={t('common.add')}> | ||||||
|  |               <CButton | ||||||
|  |                 color="primary" | ||||||
|  |                 hidden={toggleAdd === null} | ||||||
|  |                 variant="outline" | ||||||
|  |                 className="ml-2" | ||||||
|  |                 onClick={toggleAdd} | ||||||
|  |               > | ||||||
|  |                 <CIcon content={cilPlus} /> | ||||||
|  |               </CButton> | ||||||
|  |             </CPopover> | ||||||
|             <CPopover content={t('common.save')}> |             <CPopover content={t('common.save')}> | ||||||
|               <CButton color="primary" variant="outline" className="ml-2" onClick={closeAndSave}> |               <CButton color="primary" variant="outline" className="ml-2" onClick={closeAndSave}> | ||||||
|                 <CIcon content={cilSave} /> |                 <CIcon content={cilSave} /> | ||||||
| @@ -83,22 +100,29 @@ const CustomMultiModal = ({ | |||||||
|         </CModalHeader> |         </CModalHeader> | ||||||
|         <CModalBody> |         <CModalBody> | ||||||
|           {children} |           {children} | ||||||
|           <CDataTable |           {!noTable ? ( | ||||||
|             addTableClasses="ignore-overflow" |             <CDataTable | ||||||
|             items={value ?? []} |               addTableClasses="ignore-overflow" | ||||||
|             fields={columns} |               items={value ?? []} | ||||||
|             hover |               fields={columns} | ||||||
|             border |               hover | ||||||
|             scopedSlots={{ |               border | ||||||
|               remove: (item) => ( |               scopedSlots={{ | ||||||
|                 <td className="align-middle text-center"> |                 remove: (item) => ( | ||||||
|                   <CButton color="primary" variant="outline" size="sm" onClick={() => remove(item)}> |                   <td className="align-middle text-center"> | ||||||
|                     <CIcon content={cilMinus} /> |                     <CButton | ||||||
|                   </CButton> |                       color="primary" | ||||||
|                 </td> |                       variant="outline" | ||||||
|               ), |                       size="sm" | ||||||
|             }} |                       onClick={() => remove(item)} | ||||||
|           /> |                     > | ||||||
|  |                       <CIcon content={cilMinus} /> | ||||||
|  |                     </CButton> | ||||||
|  |                   </td> | ||||||
|  |                 ), | ||||||
|  |               }} | ||||||
|  |             /> | ||||||
|  |           ) : null} | ||||||
|         </CModalBody> |         </CModalBody> | ||||||
|       </CModal> |       </CModal> | ||||||
|     </CFormGroup> |     </CFormGroup> | ||||||
| @@ -117,12 +141,19 @@ CustomMultiModal.propTypes = { | |||||||
|   secondCol: PropTypes.string, |   secondCol: PropTypes.string, | ||||||
|   length: PropTypes.number.isRequired, |   length: PropTypes.number.isRequired, | ||||||
|   modalSize: PropTypes.string, |   modalSize: PropTypes.string, | ||||||
|  |   itemName: PropTypes.string.isRequired, | ||||||
|  |   noTable: PropTypes.bool, | ||||||
|  |   toggleAdd: PropTypes.func, | ||||||
|  |   reset: PropTypes.func, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| CustomMultiModal.defaultProps = { | CustomMultiModal.defaultProps = { | ||||||
|   firstCol: 6, |   firstCol: 6, | ||||||
|   secondCol: 6, |   secondCol: 6, | ||||||
|   modalSize: 'lg', |   modalSize: 'lg', | ||||||
|  |   noTable: false, | ||||||
|  |   toggleAdd: null, | ||||||
|  |   reset: null, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default CustomMultiModal; | export default CustomMultiModal; | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								src/components/Configuration/FileField/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/components/Configuration/FileField/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { CFormGroup, CCol, CLabel, CInvalidFeedback } from '@coreui/react'; | ||||||
|  |  | ||||||
|  | const textToShow = (fieldValue, fileName) => { | ||||||
|  |   if (fieldValue === '') return 'Not uploaded yet'; | ||||||
|  |   if (fileName === '') return 'Filename unavailable'; | ||||||
|  |   return `(using file: ${fileName})`; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const StringField = ({ | ||||||
|  |   fileName, | ||||||
|  |   fieldValue, | ||||||
|  |   label, | ||||||
|  |   firstCol, | ||||||
|  |   secondCol, | ||||||
|  |   errorMessage, | ||||||
|  |   extraButton, | ||||||
|  | }) => ( | ||||||
|  |   <CFormGroup row className="py-1"> | ||||||
|  |     <CLabel col sm={firstCol} htmlFor="name"> | ||||||
|  |       {label} | ||||||
|  |     </CLabel> | ||||||
|  |     <CCol className="align-middle" sm={secondCol}> | ||||||
|  |       <div className="float-left pt-2">{textToShow(fieldValue, fileName)}</div> | ||||||
|  |       <div className="float-left pl-3">{extraButton}</div> | ||||||
|  |       <CInvalidFeedback>{errorMessage}</CInvalidFeedback> | ||||||
|  |     </CCol> | ||||||
|  |   </CFormGroup> | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | StringField.propTypes = { | ||||||
|  |   fieldValue: PropTypes.string.isRequired, | ||||||
|  |   fileName: PropTypes.string.isRequired, | ||||||
|  |   label: PropTypes.string.isRequired, | ||||||
|  |   firstCol: PropTypes.string, | ||||||
|  |   secondCol: PropTypes.string, | ||||||
|  |   errorMessage: PropTypes.string, | ||||||
|  |   extraButton: PropTypes.node, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | StringField.defaultProps = { | ||||||
|  |   firstCol: 6, | ||||||
|  |   secondCol: 6, | ||||||
|  |   errorMessage: '', | ||||||
|  |   extraButton: null, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default StringField; | ||||||
| @@ -27,6 +27,16 @@ const ConfigurationSelectField = ({ | |||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (typeof field.options[0] === 'number') { | ||||||
|  |       for (const opt of field.options) { | ||||||
|  |         const value = opt; | ||||||
|  |         if (value === field.value) { | ||||||
|  |           return { value, label: opt }; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (options !== null) { |     if (options !== null) { | ||||||
|       for (const opt of options) { |       for (const opt of options) { | ||||||
|         if (field.value === opt.value) return opt; |         if (field.value === opt.value) return opt; | ||||||
| @@ -34,7 +44,10 @@ const ConfigurationSelectField = ({ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     for (const opt of field.options) { |     for (const opt of field.options) { | ||||||
|       if (field.value === opt.value) return opt; |       if (field.value === opt.value) { | ||||||
|  |         if (field.mergeOptions) return { value: opt.value, label: `${opt.label} (${opt.value})` }; | ||||||
|  |         return opt; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return null; |     return null; | ||||||
| @@ -42,10 +55,22 @@ const ConfigurationSelectField = ({ | |||||||
|  |  | ||||||
|   const parseOptions = () => { |   const parseOptions = () => { | ||||||
|     if (options !== null) return options; |     if (options !== null) return options; | ||||||
|     if (typeof field.options[0] !== 'string') { |     if (typeof field.options[0] !== 'string' && typeof field.options[0] !== 'number') { | ||||||
|  |       if (field.mergeOptions) | ||||||
|  |         return field.options.map((opt) => ({ | ||||||
|  |           value: opt.value, | ||||||
|  |           label: `${opt.label} (${opt.value})`, | ||||||
|  |         })); | ||||||
|       return field.options; |       return field.options; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (typeof field.options[0] === 'number') { | ||||||
|  |       return field.options.map((opt) => ({ | ||||||
|  |         value: opt, | ||||||
|  |         label: `${opt}`, | ||||||
|  |       })); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (field.options[0].includes('(')) { |     if (field.options[0].includes('(')) { | ||||||
|       return field.options.map((opt) => ({ |       return field.options.map((opt) => ({ | ||||||
|         value: opt.split('(')[1].split(')')[0], |         value: opt.split('(')[1].split(')')[0], | ||||||
| @@ -54,7 +79,7 @@ const ConfigurationSelectField = ({ | |||||||
|     } |     } | ||||||
|     return field.options.map((opt) => ({ |     return field.options.map((opt) => ({ | ||||||
|       value: opt, |       value: opt, | ||||||
|       label: `opt${field.unit ?? ''}`, |       label: `${opt}${field.unit ?? ''}`, | ||||||
|     })); |     })); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -64,7 +89,7 @@ const ConfigurationSelectField = ({ | |||||||
|         {label} |         {label} | ||||||
|       </CLabel> |       </CLabel> | ||||||
|       <CCol sm={secondCol}> |       <CCol sm={secondCol}> | ||||||
|         <div style={{ width: width ?? '' }}> |         <div style={{ maxWidth: width ?? '' }}> | ||||||
|           <Select |           <Select | ||||||
|             name="Subsystems" |             name="Subsystems" | ||||||
|             options={parseOptions()} |             options={parseOptions()} | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import React, { useState, useCallback } from 'react'; | import React, { useState, useCallback, useEffect } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { CFormGroup, CCol, CLabel, CInput, CInvalidFeedback } from '@coreui/react'; | import { CFormGroup, CCol, CLabel, CInput, CInvalidFeedback } from '@coreui/react'; | ||||||
| import _ from 'lodash'; | import _ from 'lodash'; | ||||||
| @@ -14,6 +14,7 @@ const StringField = ({ | |||||||
|   disabled, |   disabled, | ||||||
|   width, |   width, | ||||||
|   placeholder, |   placeholder, | ||||||
|  |   extraButton, | ||||||
| }) => { | }) => { | ||||||
|   const [localValue, setLocalValue] = useState(field.value); |   const [localValue, setLocalValue] = useState(field.value); | ||||||
|  |  | ||||||
| @@ -41,6 +42,36 @@ const StringField = ({ | |||||||
|     [localValue, debounceChange, updateField], |     [localValue, debounceChange, updateField], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (localValue !== field.value) setLocalValue(field.value); | ||||||
|  |   }, [field]); | ||||||
|  |  | ||||||
|  |   if (extraButton !== null) { | ||||||
|  |     return ( | ||||||
|  |       <CFormGroup row className="py-1"> | ||||||
|  |         <CLabel col sm={firstCol} htmlFor="name"> | ||||||
|  |           {label} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm={secondCol}> | ||||||
|  |           <div className="float-left w-75" style={{ width: width ?? '' }}> | ||||||
|  |             <CInput | ||||||
|  |               id={id} | ||||||
|  |               type="text" | ||||||
|  |               required | ||||||
|  |               value={localValue} | ||||||
|  |               onChange={handleTyping} | ||||||
|  |               invalid={field.error} | ||||||
|  |               disabled={disabled} | ||||||
|  |               placeholder={placeholder} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |           <div className="float-left pl-3">{extraButton}</div> | ||||||
|  |           <CInvalidFeedback>{errorMessage}</CInvalidFeedback> | ||||||
|  |         </CCol> | ||||||
|  |       </CFormGroup> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <CFormGroup row className="py-1"> |     <CFormGroup row className="py-1"> | ||||||
|       <CLabel col sm={firstCol} htmlFor="name"> |       <CLabel col sm={firstCol} htmlFor="name"> | ||||||
| @@ -56,7 +87,6 @@ const StringField = ({ | |||||||
|             onChange={handleTyping} |             onChange={handleTyping} | ||||||
|             invalid={field.error} |             invalid={field.error} | ||||||
|             disabled={disabled} |             disabled={disabled} | ||||||
|             maxLength="50" |  | ||||||
|             placeholder={placeholder} |             placeholder={placeholder} | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
| @@ -77,6 +107,7 @@ StringField.propTypes = { | |||||||
|   disabled: PropTypes.bool.isRequired, |   disabled: PropTypes.bool.isRequired, | ||||||
|   width: PropTypes.string, |   width: PropTypes.string, | ||||||
|   placeholder: PropTypes.string, |   placeholder: PropTypes.string, | ||||||
|  |   extraButton: PropTypes.node, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| StringField.defaultProps = { | StringField.defaultProps = { | ||||||
| @@ -85,6 +116,7 @@ StringField.defaultProps = { | |||||||
|   errorMessage: '', |   errorMessage: '', | ||||||
|   width: null, |   width: null, | ||||||
|   placeholder: null, |   placeholder: null, | ||||||
|  |   extraButton: null, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default StringField; | export default StringField; | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								src/components/ContactTable/DeleteButton.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/components/ContactTable/DeleteButton.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import ReactTooltip from 'react-tooltip'; | ||||||
|  | import { v4 as createUuid } from 'uuid'; | ||||||
|  | import { CButton, CCardBody, CCardHeader, CRow, CCol, CPopover, CButtonClose } from '@coreui/react'; | ||||||
|  | import { cilTrash } from '@coreui/icons'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import LoadingButton from '../LoadingButton'; | ||||||
|  |  | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const DeleteButton = ({ t, contact, deleteContact, hideTooltips }) => { | ||||||
|  |   const [tooltipId] = useState(createUuid()); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CPopover content={t('common.delete')}> | ||||||
|  |       <div className="d-inline"> | ||||||
|  |         <CButton | ||||||
|  |           color="primary" | ||||||
|  |           variant="outline" | ||||||
|  |           shape="square" | ||||||
|  |           size="sm" | ||||||
|  |           className="mx-2" | ||||||
|  |           data-tip | ||||||
|  |           data-for={tooltipId} | ||||||
|  |           data-event="click" | ||||||
|  |           style={{ width: '33px', height: '30px' }} | ||||||
|  |         > | ||||||
|  |           <CIcon name="cil-trash" content={cilTrash} size="sm" /> | ||||||
|  |         </CButton> | ||||||
|  |         <ReactTooltip | ||||||
|  |           id={tooltipId} | ||||||
|  |           place="top" | ||||||
|  |           effect="solid" | ||||||
|  |           globalEventOff="click" | ||||||
|  |           clickable | ||||||
|  |           className={[styles.deleteTooltip, 'tooltipRight'].join(' ')} | ||||||
|  |           border | ||||||
|  |           borderColor="#321fdb" | ||||||
|  |           arrowColor="white" | ||||||
|  |           overridePosition={({ left, top }) => { | ||||||
|  |             const element = document.getElementById(tooltipId); | ||||||
|  |             const tooltipWidth = element ? element.offsetWidth : 0; | ||||||
|  |             const newLeft = left - tooltipWidth * 0.4; | ||||||
|  |             return { top, left: newLeft }; | ||||||
|  |           }} | ||||||
|  |         > | ||||||
|  |           <CCardHeader color="primary" className={styles.tooltipHeader}> | ||||||
|  |             {t('inventory.delete_tag')} | ||||||
|  |             <CButtonClose | ||||||
|  |               style={{ color: 'white' }} | ||||||
|  |               onClick={(e) => { | ||||||
|  |                 e.target.parentNode.parentNode.classList.remove('show'); | ||||||
|  |                 hideTooltips(); | ||||||
|  |               }} | ||||||
|  |             /> | ||||||
|  |           </CCardHeader> | ||||||
|  |           <CCardBody> | ||||||
|  |             <CRow> | ||||||
|  |               <CCol> | ||||||
|  |                 <LoadingButton | ||||||
|  |                   data-toggle="dropdown" | ||||||
|  |                   variant="outline" | ||||||
|  |                   color="danger" | ||||||
|  |                   label={t('common.confirm')} | ||||||
|  |                   isLoadingLabel={t('user.deleting')} | ||||||
|  |                   isLoading={false} | ||||||
|  |                   action={() => deleteContact(contact.id)} | ||||||
|  |                   block | ||||||
|  |                   disabled={false} | ||||||
|  |                 /> | ||||||
|  |               </CCol> | ||||||
|  |             </CRow> | ||||||
|  |           </CCardBody> | ||||||
|  |         </ReactTooltip> | ||||||
|  |       </div> | ||||||
|  |     </CPopover> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | DeleteButton.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   contact: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   deleteContact: PropTypes.func.isRequired, | ||||||
|  |   hideTooltips: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default DeleteButton; | ||||||
							
								
								
									
										90
									
								
								src/components/ContactTable/UnassignButton.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/components/ContactTable/UnassignButton.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import ReactTooltip from 'react-tooltip'; | ||||||
|  | import { v4 as createUuid } from 'uuid'; | ||||||
|  | import { CButton, CCardBody, CCardHeader, CRow, CCol, CPopover, CButtonClose } from '@coreui/react'; | ||||||
|  | import { cilMinus } from '@coreui/icons'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import LoadingButton from '../LoadingButton'; | ||||||
|  |  | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const UnassignButton = ({ t, contact, unassignContact, hideTooltips, disabled }) => { | ||||||
|  |   const [tooltipId] = useState(createUuid()); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CPopover content={t('inventory.unassign')}> | ||||||
|  |       <div className="d-inline"> | ||||||
|  |         <CButton | ||||||
|  |           disabled={disabled} | ||||||
|  |           color="primary" | ||||||
|  |           variant="outline" | ||||||
|  |           shape="square" | ||||||
|  |           size="sm" | ||||||
|  |           className="mx-2" | ||||||
|  |           data-tip | ||||||
|  |           data-for={tooltipId} | ||||||
|  |           data-event="click" | ||||||
|  |           style={{ width: '33px', height: '30px' }} | ||||||
|  |         > | ||||||
|  |           <CIcon name="cil-minus" content={cilMinus} size="sm" /> | ||||||
|  |         </CButton> | ||||||
|  |         <ReactTooltip | ||||||
|  |           id={tooltipId} | ||||||
|  |           place="top" | ||||||
|  |           effect="solid" | ||||||
|  |           globalEventOff="click" | ||||||
|  |           clickable | ||||||
|  |           className={[styles.unassignTooltip, 'tooltipRight'].join(' ')} | ||||||
|  |           border | ||||||
|  |           borderColor="#321fdb" | ||||||
|  |           arrowColor="white" | ||||||
|  |           overridePosition={({ left, top }) => { | ||||||
|  |             const element = document.getElementById(tooltipId); | ||||||
|  |             const tooltipWidth = element ? element.offsetWidth : 0; | ||||||
|  |             const newLeft = left - tooltipWidth * 0.4; | ||||||
|  |             return { top, left: newLeft }; | ||||||
|  |           }} | ||||||
|  |         > | ||||||
|  |           <CCardHeader color="primary" className={styles.tooltipHeader}> | ||||||
|  |             {t('inventory.unassign_tag')} | ||||||
|  |             <CButtonClose | ||||||
|  |               style={{ color: 'white' }} | ||||||
|  |               onClick={(e) => { | ||||||
|  |                 e.target.parentNode.parentNode.classList.remove('show'); | ||||||
|  |                 hideTooltips(); | ||||||
|  |               }} | ||||||
|  |             /> | ||||||
|  |           </CCardHeader> | ||||||
|  |           <CCardBody> | ||||||
|  |             <CRow> | ||||||
|  |               <CCol> | ||||||
|  |                 <LoadingButton | ||||||
|  |                   data-toggle="dropdown" | ||||||
|  |                   variant="outline" | ||||||
|  |                   color="danger" | ||||||
|  |                   label={t('common.confirm')} | ||||||
|  |                   isLoadingLabel={t('user.deleting')} | ||||||
|  |                   isLoading={false} | ||||||
|  |                   action={() => unassignContact(contact.serialNumber)} | ||||||
|  |                   block | ||||||
|  |                   disabled={false} | ||||||
|  |                 /> | ||||||
|  |               </CCol> | ||||||
|  |             </CRow> | ||||||
|  |           </CCardBody> | ||||||
|  |         </ReactTooltip> | ||||||
|  |       </div> | ||||||
|  |     </CPopover> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | UnassignButton.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   contact: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   unassignContact: PropTypes.func.isRequired, | ||||||
|  |   hideTooltips: PropTypes.func.isRequired, | ||||||
|  |   disabled: PropTypes.bool.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default UnassignButton; | ||||||
							
								
								
									
										181
									
								
								src/components/ContactTable/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/components/ContactTable/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | |||||||
|  | import React, { useEffect } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { CButton, CDataTable, CLink, CPopover, CButtonToolbar } from '@coreui/react'; | ||||||
|  | import { cilPencil, cilPlus } from '@coreui/icons'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import ReactTooltip from 'react-tooltip'; | ||||||
|  | import DeleteButton from './DeleteButton'; | ||||||
|  | import UnassignButton from './UnassignButton'; | ||||||
|  | import FormattedDate from '../FormattedDate'; | ||||||
|  |  | ||||||
|  | const ContactTable = ({ | ||||||
|  |   t, | ||||||
|  |   loading, | ||||||
|  |   entity, | ||||||
|  |   filterOnEntity, | ||||||
|  |   contacts, | ||||||
|  |   unassign, | ||||||
|  |   assignToEntity, | ||||||
|  |   toggleEditModal, | ||||||
|  |   deleteContact, | ||||||
|  |   perPageSwitcher, | ||||||
|  |   pageSwitcher, | ||||||
|  | }) => { | ||||||
|  |   const columns = filterOnEntity | ||||||
|  |     ? [ | ||||||
|  |         { key: 'primaryEmail', label: t('contact.primary_email'), _style: { width: '15%' } }, | ||||||
|  |         { key: 'name', label: t('user.name'), _style: { width: '10%' } }, | ||||||
|  |         { key: 'description', label: t('user.description'), _style: { width: '20%' } }, | ||||||
|  |         { key: 'created', label: t('common.created'), _style: { width: '10%' } }, | ||||||
|  |         { key: 'actions', label: t('actions.actions'), _style: { width: '1%' } }, | ||||||
|  |       ] | ||||||
|  |     : [ | ||||||
|  |         { key: 'primaryEmail', label: t('contact.primary_email'), _style: { width: '15%' } }, | ||||||
|  |         { key: 'name', label: t('user.name'), _style: { width: '10%' } }, | ||||||
|  |         { key: 'description', label: t('user.description'), _style: { width: '20%' } }, | ||||||
|  |         { key: 'entity', label: t('entity.entity'), _style: { width: '10%' } }, | ||||||
|  |         { key: 'created', label: t('common.created'), _style: { width: '12%' } }, | ||||||
|  |         { key: 'modified', label: t('common.modified'), _style: { width: '12%' } }, | ||||||
|  |         { key: 'actions', label: t('actions.actions'), _style: { width: '1%' } }, | ||||||
|  |       ]; | ||||||
|  |  | ||||||
|  |   const hideTooltips = () => ReactTooltip.hide(); | ||||||
|  |  | ||||||
|  |   const escFunction = (event) => { | ||||||
|  |     if (event.keyCode === 27) { | ||||||
|  |       hideTooltips(); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     document.addEventListener('keydown', escFunction, false); | ||||||
|  |  | ||||||
|  |     return () => { | ||||||
|  |       document.removeEventListener('keydown', escFunction, false); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <CDataTable | ||||||
|  |         addTableClasses="ignore-overflow" | ||||||
|  |         items={contacts} | ||||||
|  |         fields={columns} | ||||||
|  |         hover | ||||||
|  |         border | ||||||
|  |         loading={loading} | ||||||
|  |         scopedSlots={{ | ||||||
|  |           name: (item) => ( | ||||||
|  |             <td className="align-middle"> | ||||||
|  |               {item.firstname} {item.lastname} | ||||||
|  |             </td> | ||||||
|  |           ), | ||||||
|  |           created: (item) => ( | ||||||
|  |             <td className="align-middle"> | ||||||
|  |               <FormattedDate date={item.created} /> | ||||||
|  |             </td> | ||||||
|  |           ), | ||||||
|  |           modified: (item) => ( | ||||||
|  |             <td className="align-middle"> | ||||||
|  |               <FormattedDate date={item.modified} /> | ||||||
|  |             </td> | ||||||
|  |           ), | ||||||
|  |           description: (item) => <td className="align-middle">{item.description}</td>, | ||||||
|  |           entity: (item) => ( | ||||||
|  |             <td className="align-middle"> | ||||||
|  |               {filterOnEntity ? ( | ||||||
|  |                 item.entity | ||||||
|  |               ) : ( | ||||||
|  |                 <CLink | ||||||
|  |                   className="c-subheader-nav-link" | ||||||
|  |                   aria-current="page" | ||||||
|  |                   to={() => `/entity/${item.entity}`} | ||||||
|  |                 > | ||||||
|  |                   {item.extendedInfo?.entity?.name ?? item.entity} | ||||||
|  |                 </CLink> | ||||||
|  |               )} | ||||||
|  |             </td> | ||||||
|  |           ), | ||||||
|  |           actions: (item) => ( | ||||||
|  |             <td className="text-center align-middle py-0"> | ||||||
|  |               <CButtonToolbar | ||||||
|  |                 role="group" | ||||||
|  |                 className="justify-content-flex-end" | ||||||
|  |                 style={{ width: '190px' }} | ||||||
|  |               > | ||||||
|  |                 <CPopover content={t('inventory.assign_ent_ven')}> | ||||||
|  |                   <div> | ||||||
|  |                     <CButton | ||||||
|  |                       disabled={entity === null || filterOnEntity} | ||||||
|  |                       color="primary" | ||||||
|  |                       variant="outline" | ||||||
|  |                       shape="square" | ||||||
|  |                       size="sm" | ||||||
|  |                       className="mx-2" | ||||||
|  |                       onClick={() => assignToEntity(item.id)} | ||||||
|  |                       style={{ width: '33px', height: '30px' }} | ||||||
|  |                     > | ||||||
|  |                       <CIcon content={cilPlus} /> | ||||||
|  |                     </CButton> | ||||||
|  |                   </div> | ||||||
|  |                 </CPopover> | ||||||
|  |                 <UnassignButton | ||||||
|  |                   t={t} | ||||||
|  |                   contact={item} | ||||||
|  |                   unassignTag={unassign} | ||||||
|  |                   hideTooltips={hideTooltips} | ||||||
|  |                   disabled={entity === null || !entity.isVenue} | ||||||
|  |                 /> | ||||||
|  |                 <DeleteButton | ||||||
|  |                   t={t} | ||||||
|  |                   contact={item} | ||||||
|  |                   deleteContact={deleteContact} | ||||||
|  |                   hideTooltips={hideTooltips} | ||||||
|  |                 /> | ||||||
|  |                 <CPopover content="Edit Tag"> | ||||||
|  |                   <CButton | ||||||
|  |                     color="primary" | ||||||
|  |                     variant="outline" | ||||||
|  |                     shape="square" | ||||||
|  |                     size="sm" | ||||||
|  |                     className="ml-2" | ||||||
|  |                     onClick={() => toggleEditModal(item.id)} | ||||||
|  |                     style={{ width: '33px', height: '30px' }} | ||||||
|  |                   > | ||||||
|  |                     <CIcon name="cil-pencil" content={cilPencil} size="sm" /> | ||||||
|  |                   </CButton> | ||||||
|  |                 </CPopover> | ||||||
|  |               </CButtonToolbar> | ||||||
|  |             </td> | ||||||
|  |           ), | ||||||
|  |         }} | ||||||
|  |       /> | ||||||
|  |       <div className="pl-3"> | ||||||
|  |         {pageSwitcher} | ||||||
|  |         <p className="float-left pr-2 pt-1">{t('common.items_per_page')}</p> | ||||||
|  |         {perPageSwitcher} | ||||||
|  |       </div> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ContactTable.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   loading: PropTypes.bool.isRequired, | ||||||
|  |   entity: PropTypes.instanceOf(Object), | ||||||
|  |   filterOnEntity: PropTypes.bool, | ||||||
|  |   contacts: PropTypes.instanceOf(Array).isRequired, | ||||||
|  |   unassign: PropTypes.func.isRequired, | ||||||
|  |   assignToEntity: PropTypes.func.isRequired, | ||||||
|  |   toggleEditModal: PropTypes.func.isRequired, | ||||||
|  |   deleteContact: PropTypes.func.isRequired, | ||||||
|  |   perPageSwitcher: PropTypes.node.isRequired, | ||||||
|  |   pageSwitcher: PropTypes.node.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ContactTable.defaultProps = { | ||||||
|  |   filterOnEntity: false, | ||||||
|  |   entity: null, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default ContactTable; | ||||||
							
								
								
									
										28
									
								
								src/components/ContactTable/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/components/ContactTable/index.module.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | .unassignTooltip { | ||||||
|  |   opacity: 1 !important; | ||||||
|  |   padding: 0px 0px 0px 0px !important; | ||||||
|  |   border-radius: 1rem !important; | ||||||
|  |   background-color: #fff !important; | ||||||
|  |   border-color: #321fdb !important; | ||||||
|  |   font-size: 0.875rem !important; | ||||||
|  |   font-weight: 400 !important; | ||||||
|  |   box-shadow: 0 3px 10px rgb(0 0 0 / 0.2) !important; | ||||||
|  |   width: 300px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .deleteTooltip { | ||||||
|  |   opacity: 1 !important; | ||||||
|  |   padding: 0px 0px 0px 0px !important; | ||||||
|  |   border-radius: 1rem !important; | ||||||
|  |   background-color: #fff !important; | ||||||
|  |   border-color: #321fdb !important; | ||||||
|  |   font-size: 0.875rem !important; | ||||||
|  |   font-weight: 400 !important; | ||||||
|  |   box-shadow: 0 3px 10px rgb(0 0 0 / 0.2) !important; | ||||||
|  |   width: 200px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tooltipHeader { | ||||||
|  |   border-top-left-radius: 1rem !important; | ||||||
|  |   border-top-right-radius: 1rem !important; | ||||||
|  | } | ||||||
| @@ -46,8 +46,11 @@ const CreateUserForm = ({ t, fields, updateField, policies, toggleChange }) => { | |||||||
|         </CLabel> |         </CLabel> | ||||||
|         <CCol sm="4"> |         <CCol sm="4"> | ||||||
|           <CSelect custom id="userRole" defaultValue="Admin" onChange={updateField}> |           <CSelect custom id="userRole" defaultValue="Admin" onChange={updateField}> | ||||||
|  |             <option value="accounting">Accounting</option> | ||||||
|             <option value="admin">Admin</option> |             <option value="admin">Admin</option> | ||||||
|             <option value="csr">CSR</option> |             <option value="csr">CSR</option> | ||||||
|  |             <option value="installer">Installer</option> | ||||||
|  |             <option value="noc">NOC</option> | ||||||
|             <option value="root">Root</option> |             <option value="root">Root</option> | ||||||
|             <option value="special">Special</option> |             <option value="special">Special</option> | ||||||
|             <option value="sub">Sub</option> |             <option value="sub">Sub</option> | ||||||
|   | |||||||
| @@ -85,10 +85,10 @@ const EditConfigurationForm = ({ | |||||||
|       <CFormGroup row> |       <CFormGroup row> | ||||||
|         <CCol> |         <CCol> | ||||||
|           <CRow className="pb-0"> |           <CRow className="pb-0"> | ||||||
|             <CLabel md="3" col htmlFor="name"> |             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|               <div>{t('user.name')}:</div> |               <div>{t('user.name')}:</div> | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="9"> |             <CCol lg="7" xl="9"> | ||||||
|               {editing ? ( |               {editing ? ( | ||||||
|                 <div> |                 <div> | ||||||
|                   <CInput |                   <CInput | ||||||
| @@ -111,10 +111,10 @@ const EditConfigurationForm = ({ | |||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="pb-0"> |           <CRow className="pb-0"> | ||||||
|             <CLabel md="3" col htmlFor="name"> |             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|               <div>{t('user.description')}:</div> |               <div>{t('user.description')}:</div> | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="9"> |             <CCol lg="7" xl="9"> | ||||||
|               {editing ? ( |               {editing ? ( | ||||||
|                 <div> |                 <div> | ||||||
|                   <CInput |                   <CInput | ||||||
| @@ -135,10 +135,10 @@ const EditConfigurationForm = ({ | |||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="pt-1"> |           <CRow className="pt-1"> | ||||||
|             <CLabel md="3" col htmlFor="name"> |             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|               <div>{t('configuration.device_types')}:</div> |               <div>{t('configuration.device_types')}:</div> | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="9"> |             <CCol lg="7" xl="9"> | ||||||
|               <Select |               <Select | ||||||
|                 isMulti |                 isMulti | ||||||
|                 closeMenuOnSelect={false} |                 closeMenuOnSelect={false} | ||||||
| @@ -156,10 +156,10 @@ const EditConfigurationForm = ({ | |||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="pt-1 pb-0"> |           <CRow className="pt-1 pb-0"> | ||||||
|             <CLabel md="3" col htmlFor="name"> |             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|               <div>RRM:</div> |               <div>RRM:</div> | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="9"> |             <CCol lg="7" xl="9"> | ||||||
|               <div style={{ width: '120px' }}> |               <div style={{ width: '120px' }}> | ||||||
|                 <Select |                 <Select | ||||||
|                   id="rrm" |                   id="rrm" | ||||||
| @@ -179,10 +179,10 @@ const EditConfigurationForm = ({ | |||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="py-1"> |           <CRow className="py-1"> | ||||||
|             <CLabel col md="4" xxl="3" htmlFor="firmwareUpgrade"> |             <CLabel col lg="5" xl="3" htmlFor="firmwareUpgrade"> | ||||||
|               Firmware Upgrade |               Firmware Upgrade | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="8" xxl="9"> |             <CCol lg="7" xl="9"> | ||||||
|               <div style={{ width: '120px' }}> |               <div style={{ width: '120px' }}> | ||||||
|                 <Select |                 <Select | ||||||
|                   id="rrm" |                   id="rrm" | ||||||
| @@ -202,10 +202,10 @@ const EditConfigurationForm = ({ | |||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="py-1"> |           <CRow className="py-1"> | ||||||
|             <CLabel col md="3" htmlFor="firmwareRCOnly"> |             <CLabel col lg="5" xl="3" htmlFor="firmwareRCOnly"> | ||||||
|               Only Release Candidates |               Only Release Candidates | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol xxl="9"> |             <CCol lg="7" xl="9"> | ||||||
|               <CSwitch |               <CSwitch | ||||||
|                 id="firmwareRCOnly" |                 id="firmwareRCOnly" | ||||||
|                 color="primary" |                 color="primary" | ||||||
| @@ -219,10 +219,10 @@ const EditConfigurationForm = ({ | |||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="pb-0"> |           <CRow className="pb-0"> | ||||||
|             <CLabel md="3" col htmlFor="name"> |             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|               <div>{t('configuration.used_by')}:</div> |               <div>{t('configuration.used_by')}:</div> | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="9"> |             <CCol lg="7" xl="9"> | ||||||
|               <CButton |               <CButton | ||||||
|                 disabled={fields.inUse.value.length === 0} |                 disabled={fields.inUse.value.length === 0} | ||||||
|                 className="ml-0 pl-0" |                 className="ml-0 pl-0" | ||||||
| @@ -236,20 +236,20 @@ const EditConfigurationForm = ({ | |||||||
|         </CCol> |         </CCol> | ||||||
|         <CCol className="mt-2"> |         <CCol className="mt-2"> | ||||||
|           <CRow className="pb-0"> |           <CRow className="pb-0"> | ||||||
|             <CLabel md="3" col htmlFor="name"> |             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|               <div>{t('common.created')}:</div> |               <div>{t('common.created')}:</div> | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="9"> |             <CCol lg="7" xl="9"> | ||||||
|               <p className="mt-2 mb-0"> |               <p className="mt-2 mb-0"> | ||||||
|                 <FormattedDate date={fields.created.value} /> |                 <FormattedDate date={fields.created.value} /> | ||||||
|               </p> |               </p> | ||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|           <CRow className="pb-0"> |           <CRow className="pb-0"> | ||||||
|             <CLabel md="3" col htmlFor="name"> |             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|               <div>{t('common.modified')}:</div> |               <div>{t('common.modified')}:</div> | ||||||
|             </CLabel> |             </CLabel> | ||||||
|             <CCol md="9"> |             <CCol lg="7" xl="9"> | ||||||
|               <p className="mt-2 mb-0"> |               <p className="mt-2 mb-0"> | ||||||
|                 <FormattedDate date={fields.modified.value} /> |                 <FormattedDate date={fields.modified.value} /> | ||||||
|               </p> |               </p> | ||||||
|   | |||||||
							
								
								
									
										363
									
								
								src/components/EditContactForm/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								src/components/EditContactForm/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,363 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import Select from 'react-select'; | ||||||
|  | import CreatableSelect from 'react-select/creatable'; | ||||||
|  | import { | ||||||
|  |   CForm, | ||||||
|  |   CInput, | ||||||
|  |   CLabel, | ||||||
|  |   CCol, | ||||||
|  |   CFormGroup, | ||||||
|  |   CInvalidFeedback, | ||||||
|  |   CFormText, | ||||||
|  |   CRow, | ||||||
|  |   CDataTable, | ||||||
|  |   CLink, | ||||||
|  |   CPopover, | ||||||
|  |   CButton, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { cilPlus } from '@coreui/icons'; | ||||||
|  | import FormattedDate from '../FormattedDate'; | ||||||
|  | import NotesTable from '../NotesTable'; | ||||||
|  |  | ||||||
|  | const EditContactForm = ({ | ||||||
|  |   t, | ||||||
|  |   disable, | ||||||
|  |   fields, | ||||||
|  |   updateField, | ||||||
|  |   updateFieldWithKey, | ||||||
|  |   entities, | ||||||
|  |   addNote, | ||||||
|  |   batchSetField, | ||||||
|  |   hideEntities, | ||||||
|  | }) => { | ||||||
|  |   const [filter, setFilter] = useState(''); | ||||||
|  |  | ||||||
|  |   const onPhonesChange = (v) => updateFieldWithKey('phones', { value: v.map((obj) => obj.value) }); | ||||||
|  |   const onMobilesChange = (v) => | ||||||
|  |     updateFieldWithKey('mobiles', { value: v.map((obj) => obj.value) }); | ||||||
|  |  | ||||||
|  |   const NoOptionsMessage = () => ( | ||||||
|  |     <h6 className="text-center pt-2">{t('common.type_for_options')}</h6> | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const columns = [ | ||||||
|  |     { key: 'created', label: t('common.created'), _style: { width: '20%' }, filter: false }, | ||||||
|  |     { key: 'name', label: t('user.name'), _style: { width: '25%' }, filter: false }, | ||||||
|  |     { key: 'description', label: t('user.description'), _style: { width: '50%' } }, | ||||||
|  |     { key: 'actions', label: '', _style: { width: '15%' }, filter: false }, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   const selectEntity = ({ id, name }) => { | ||||||
|  |     batchSetField([ | ||||||
|  |       { id: 'entity', value: id }, | ||||||
|  |       { id: 'entityName', value: name }, | ||||||
|  |     ]); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CForm> | ||||||
|  |       <CRow> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="type"> | ||||||
|  |           {t('contact.type')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <div style={{ width: '200px' }}> | ||||||
|  |             <Select | ||||||
|  |               id="type" | ||||||
|  |               value={ | ||||||
|  |                 fields.type.value !== '' | ||||||
|  |                   ? { value: fields.type.value, label: fields.type.value } | ||||||
|  |                   : null | ||||||
|  |               } | ||||||
|  |               onChange={(v) => updateFieldWithKey('type', { value: v.value })} | ||||||
|  |               options={[ | ||||||
|  |                 { label: 'SUBSCRIBER', value: 'SUBSCRIBER' }, | ||||||
|  |                 { label: 'USER', value: 'USER' }, | ||||||
|  |                 { label: 'INSTALLER', value: 'INSTALLER' }, | ||||||
|  |                 { label: 'CSR', value: 'CSR' }, | ||||||
|  |                 { label: 'MANAGER', value: 'MANAGER' }, | ||||||
|  |                 { label: 'BUSINESSOWNER', value: 'BUSINESSOWNER' }, | ||||||
|  |                 { label: 'TECHNICIAN', value: 'TECHNICIAN' }, | ||||||
|  |                 { label: 'CORPORATE', value: 'CORPORATE' }, | ||||||
|  |               ]} | ||||||
|  |               isDisabled={disable} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |           <CFormText color={fields.type.error ? 'danger' : ''}>{t('common.required')}</CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="title"> | ||||||
|  |           {t('contact.user_title')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="title" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.title.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.title.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="salutation"> | ||||||
|  |           {t('contact.salutation')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <div style={{ width: '120px' }}> | ||||||
|  |             <Select | ||||||
|  |               id="salutation" | ||||||
|  |               value={{ | ||||||
|  |                 value: fields.salutation.value, | ||||||
|  |                 label: fields.salutation.value === '' ? 'None' : fields.salutation.value, | ||||||
|  |               }} | ||||||
|  |               onChange={(v) => updateFieldWithKey('salutation', { value: v.value })} | ||||||
|  |               options={[ | ||||||
|  |                 { label: 'None', value: '' }, | ||||||
|  |                 { label: 'Mr.', value: 'Mr.' }, | ||||||
|  |                 { label: 'Ms.', value: 'Ms.' }, | ||||||
|  |                 { label: 'Mx.', value: 'Mx.' }, | ||||||
|  |                 { label: 'Dr.', value: 'Dr.' }, | ||||||
|  |               ]} | ||||||
|  |               isDisabled={disable} | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="accessPIN"> | ||||||
|  |           {t('contact.access_pin')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="accessPIN" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.accessPIN.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.accessPIN.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CInvalidFeedback>{t('common.required')}</CInvalidFeedback> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="firstname"> | ||||||
|  |           {t('contact.first_name')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="firstname" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.firstname.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.firstname.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CFormText color={fields.firstname.error ? 'danger' : ''}> | ||||||
|  |             {t('common.required')} | ||||||
|  |           </CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="lastname"> | ||||||
|  |           {t('contact.last_name')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="lastname" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.lastname.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.lastname.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CFormText color={fields.lastname.error ? 'danger' : ''}> | ||||||
|  |             {t('common.required')} | ||||||
|  |           </CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="initials"> | ||||||
|  |           {t('contact.initials')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="initials" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.initials.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel sm="2" col htmlFor="visual"> | ||||||
|  |           {t('contact.visual')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="visual" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.visual.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="primaryEmail"> | ||||||
|  |           {t('contact.primary_email')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="primaryEmail" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.primaryEmail.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.primaryEmail.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CFormText color={fields.primaryEmail.error ? 'danger' : ''}> | ||||||
|  |             {t('common.required')} | ||||||
|  |           </CFormText> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="secondaryEmail"> | ||||||
|  |           {t('contact.secondary_email')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="secondaryEmail" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.secondaryEmail.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             invalid={fields.secondaryEmail.error} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |           <CInvalidFeedback>{t('common.required')}</CInvalidFeedback> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="phones"> | ||||||
|  |           Landlines | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CreatableSelect | ||||||
|  |             isMulti | ||||||
|  |             id="phones" | ||||||
|  |             isDisabled={disable} | ||||||
|  |             onChange={onPhonesChange} | ||||||
|  |             components={{ NoOptionsMessage }} | ||||||
|  |             options={[]} | ||||||
|  |             value={fields.phones.value.map((opt) => ({ value: opt, label: opt }))} | ||||||
|  |             placeholder={t('common.type_for_options')} | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel className="mb-5" sm="2" col htmlFor="phones"> | ||||||
|  |           Mobiles | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CreatableSelect | ||||||
|  |             id="mobiles" | ||||||
|  |             isMulti | ||||||
|  |             isDisabled={disable} | ||||||
|  |             onChange={onMobilesChange} | ||||||
|  |             components={{ NoOptionsMessage }} | ||||||
|  |             options={[]} | ||||||
|  |             value={fields.mobiles.value.map((opt) => ({ value: opt, label: opt }))} | ||||||
|  |             placeholder={t('common.type_for_options')} | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |         <CLabel sm="2" col htmlFor="description"> | ||||||
|  |           {t('user.description')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4"> | ||||||
|  |           <CInput | ||||||
|  |             id="description" | ||||||
|  |             type="text" | ||||||
|  |             required | ||||||
|  |             value={fields.description.value} | ||||||
|  |             onChange={updateField} | ||||||
|  |             disabled={disable} | ||||||
|  |             maxLength="50" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|  |       </CRow> | ||||||
|  |       <CFormGroup row className="pb-1"> | ||||||
|  |         <CLabel sm="2" col htmlFor="title"> | ||||||
|  |           {t('entity.selected_entity')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol sm="4" className="pt-2"> | ||||||
|  |           <h6> | ||||||
|  |             {fields.entity.value === '' ? t('entity.need_select_entity') : fields.entityName.value} | ||||||
|  |           </h6> | ||||||
|  |         </CCol> | ||||||
|  |       </CFormGroup> | ||||||
|  |       {hideEntities ? null : ( | ||||||
|  |         <div className="overflow-auto border mb-3" style={{ height: '200px' }}> | ||||||
|  |           <CInput | ||||||
|  |             className="w-50 mb-2" | ||||||
|  |             type="text" | ||||||
|  |             placeholder="Search" | ||||||
|  |             value={filter} | ||||||
|  |             onChange={(e) => setFilter(e.target.value)} | ||||||
|  |           /> | ||||||
|  |           <CDataTable | ||||||
|  |             items={entities} | ||||||
|  |             fields={columns} | ||||||
|  |             hover | ||||||
|  |             tableFilterValue={filter} | ||||||
|  |             border | ||||||
|  |             scopedSlots={{ | ||||||
|  |               name: (item) => ( | ||||||
|  |                 <td> | ||||||
|  |                   <CLink | ||||||
|  |                     className="c-subheader-nav-link" | ||||||
|  |                     aria-current="page" | ||||||
|  |                     to={() => `/entity/${item.id}`} | ||||||
|  |                   > | ||||||
|  |                     {item.name} | ||||||
|  |                   </CLink> | ||||||
|  |                 </td> | ||||||
|  |               ), | ||||||
|  |               created: (item) => ( | ||||||
|  |                 <td> | ||||||
|  |                   <FormattedDate date={item.created} /> | ||||||
|  |                 </td> | ||||||
|  |               ), | ||||||
|  |               actions: (item) => ( | ||||||
|  |                 <td> | ||||||
|  |                   <CPopover content={t('entity.select_entity')}> | ||||||
|  |                     <CButton color="primary" variant="outline" onClick={() => selectEntity(item)}> | ||||||
|  |                       <CIcon content={cilPlus} /> | ||||||
|  |                     </CButton> | ||||||
|  |                   </CPopover> | ||||||
|  |                 </td> | ||||||
|  |               ), | ||||||
|  |             }} | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |       )} | ||||||
|  |       <NotesTable t={t} notes={fields.notes.value} addNote={addNote} loading={disable} editable /> | ||||||
|  |     </CForm> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | EditContactForm.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   disable: PropTypes.bool.isRequired, | ||||||
|  |   fields: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   updateField: PropTypes.func.isRequired, | ||||||
|  |   updateFieldWithKey: PropTypes.func.isRequired, | ||||||
|  |   entities: PropTypes.instanceOf(Array).isRequired, | ||||||
|  |   addNote: PropTypes.func.isRequired, | ||||||
|  |   batchSetField: PropTypes.func.isRequired, | ||||||
|  |   hideEntities: PropTypes.bool, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | EditContactForm.defaultProps = { | ||||||
|  |   hideEntities: false, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default EditContactForm; | ||||||
| @@ -31,10 +31,10 @@ const EditEntityForm = ({ | |||||||
|     <CFormGroup row> |     <CFormGroup row> | ||||||
|       <CCol> |       <CCol> | ||||||
|         <CRow className="pb-0"> |         <CRow className="pb-0"> | ||||||
|           <CLabel md="4" xxl="3" col htmlFor="name"> |           <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|             <div>{t('user.name')}:</div> |             <div>{t('user.name')}:</div> | ||||||
|           </CLabel> |           </CLabel> | ||||||
|           <CCol md="8" xxl="9"> |           <CCol lg="7" xxl="9"> | ||||||
|             {editing ? ( |             {editing ? ( | ||||||
|               <div> |               <div> | ||||||
|                 <CInput |                 <CInput | ||||||
| @@ -57,10 +57,10 @@ const EditEntityForm = ({ | |||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <CRow className="pb-0"> |         <CRow className="pb-0"> | ||||||
|           <CLabel md="4" xxl="3" col htmlFor="name"> |           <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|             <div>{t('user.description')}:</div> |             <div>{t('user.description')}:</div> | ||||||
|           </CLabel> |           </CLabel> | ||||||
|           <CCol md="8" xxl="9"> |           <CCol lg="7" xxl="9"> | ||||||
|             {editing ? ( |             {editing ? ( | ||||||
|               <div> |               <div> | ||||||
|                 <CInput |                 <CInput | ||||||
| @@ -81,10 +81,10 @@ const EditEntityForm = ({ | |||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <CRow className="pb-0"> |         <CRow className="pb-0"> | ||||||
|           <CLabel md="4" xxl="3" col htmlFor="name"> |           <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|             <div>RRM:</div> |             <div>RRM:</div> | ||||||
|           </CLabel> |           </CLabel> | ||||||
|           <CCol md="8" xxl="9"> |           <CCol lg="7" xxl="9"> | ||||||
|             <div style={{ width: '120px' }}> |             <div style={{ width: '120px' }}> | ||||||
|               <Select |               <Select | ||||||
|                 id="rrm" |                 id="rrm" | ||||||
| @@ -101,10 +101,10 @@ const EditEntityForm = ({ | |||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <CRow className="pb-0"> |         <CRow className="pb-0"> | ||||||
|           <CLabel md="4" xxl="3" col htmlFor="name"> |           <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|             <div>{t('configuration.title')}:</div> |             <div>{t('configuration.title')}:</div> | ||||||
|           </CLabel> |           </CLabel> | ||||||
|           <CCol md="8" xxl="9"> |           <CCol lg="7" xxl="9"> | ||||||
|             {editing ? ( |             {editing ? ( | ||||||
|               <CButton className="pl-0 text-left" color="link" onClick={toggleAssociate}> |               <CButton className="pl-0 text-left" color="link" onClick={toggleAssociate}> | ||||||
|                 {fields.deviceConfiguration.value === '' |                 {fields.deviceConfiguration.value === '' | ||||||
| @@ -129,10 +129,10 @@ const EditEntityForm = ({ | |||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <CRow className="pb-0"> |         <CRow className="pb-0"> | ||||||
|           <CLabel md="4" xxl="3" col htmlFor="sourceIp"> |           <CLabel lg="5" xl="3" col htmlFor="sourceIp"> | ||||||
|             <div>{t('entity.ip_detection')}:</div> |             <div>{t('entity.ip_detection')}:</div> | ||||||
|           </CLabel> |           </CLabel> | ||||||
|           <CCol md="8" xxl="9"> |           <CCol lg="7" xxl="9"> | ||||||
|             {editing ? ( |             {editing ? ( | ||||||
|               <CButton className="pl-0 text-left" color="link" onClick={toggleIpModal}> |               <CButton className="pl-0 text-left" color="link" onClick={toggleIpModal}> | ||||||
|                 {fields.sourceIP.value.length === 0 |                 {fields.sourceIP.value.length === 0 | ||||||
| @@ -151,10 +151,10 @@ const EditEntityForm = ({ | |||||||
|           </CCol> |           </CCol> | ||||||
|         </CRow> |         </CRow> | ||||||
|         <CRow className="pb-0"> |         <CRow className="pb-0"> | ||||||
|           <CLabel md="4" xxl="3" col htmlFor="name"> |           <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|             <div>{t('common.created')}:</div> |             <div>{t('common.created')}:</div> | ||||||
|           </CLabel> |           </CLabel> | ||||||
|           <CCol md="8" xxl="9"> |           <CCol lg="7" xxl="9"> | ||||||
|             <div className="mt-2 mb-0"> |             <div className="mt-2 mb-0"> | ||||||
|               <FormattedDate date={fields.created.value} /> |               <FormattedDate date={fields.created.value} /> | ||||||
|             </div> |             </div> | ||||||
| @@ -163,10 +163,10 @@ const EditEntityForm = ({ | |||||||
|       </CCol> |       </CCol> | ||||||
|       <CCol className="mt-1"> |       <CCol className="mt-1"> | ||||||
|         <CRow className="pb-0"> |         <CRow className="pb-0"> | ||||||
|           <CLabel md="4" xxl="3" col htmlFor="name"> |           <CLabel lg="5" xl="3" col htmlFor="name"> | ||||||
|             <div>{t('common.modified')}:</div> |             <div>{t('common.modified')}:</div> | ||||||
|           </CLabel> |           </CLabel> | ||||||
|           <CCol md="8" xxl="9"> |           <CCol lg="7" xxl="9"> | ||||||
|             <div className="mt-2 mb-0"> |             <div className="mt-2 mb-0"> | ||||||
|               <FormattedDate date={fields.modified.value} /> |               <FormattedDate date={fields.modified.value} /> | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
| @@ -16,15 +16,19 @@ import { | |||||||
|   CInputFile, |   CInputFile, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
|  | import parsePhoneNumber from 'libphonenumber-js'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
|  | import ValidatePhoneNumberModal from 'components/ValidatePhoneNumberModal'; | ||||||
| import NotesTable from '../NotesTable'; | import NotesTable from '../NotesTable'; | ||||||
| import LoadingButton from '../LoadingButton'; | import LoadingButton from '../LoadingButton'; | ||||||
| import Avatar from '../Avatar'; | import Avatar from '../Avatar'; | ||||||
|  | import useToggle from '../../hooks/useToggle'; | ||||||
|  |  | ||||||
| const EditMyProfile = ({ | const EditMyProfile = ({ | ||||||
|   t, |   t, | ||||||
|   user, |   user, | ||||||
|   updateUserWithId, |   updateUserWithId, | ||||||
|  |   updateWithKey, | ||||||
|   loading, |   loading, | ||||||
|   policies, |   policies, | ||||||
|   addNote, |   addNote, | ||||||
| @@ -33,26 +37,49 @@ const EditMyProfile = ({ | |||||||
|   deleteAvatar, |   deleteAvatar, | ||||||
|   showPreview, |   showPreview, | ||||||
|   fileInputKey, |   fileInputKey, | ||||||
|  |   sendPhoneNumberTest, | ||||||
|  |   testVerificationCode, | ||||||
| }) => { | }) => { | ||||||
|  |   const [showPhoneModal, togglePhoneModal] = useToggle(false); | ||||||
|   const [showPassword, setShowPassword] = useState(false); |   const [showPassword, setShowPassword] = useState(false); | ||||||
|  |  | ||||||
|   const toggleShowPassword = () => { |   const toggleShowPassword = () => { | ||||||
|     setShowPassword(!showPassword); |     setShowPassword(!showPassword); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const saveNumber = (newNumber) => { | ||||||
|  |     const newUserTypeInfo = { ...user.userTypeProprietaryInfo.value }; | ||||||
|  |     newUserTypeInfo.mobiles[0] = { number: `+${newNumber}` }; | ||||||
|  |     updateWithKey('userTypeProprietaryInfo', newUserTypeInfo); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const parseNumber = () => { | ||||||
|  |     if (user.userTypeProprietaryInfo.value.mobiles?.length > 0) { | ||||||
|  |       const phoneNumber = parsePhoneNumber( | ||||||
|  |         `${user.userTypeProprietaryInfo.value.mobiles[0].number}`, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       if (phoneNumber) { | ||||||
|  |         return phoneNumber.formatInternational(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return t('user.add_phone_number'); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <CForm> |     <CForm> | ||||||
|       <CFormGroup row> |       <CFormGroup row> | ||||||
|         <CLabel sm="2" col htmlFor="name"> |         <CLabel lg="2" xxl="1" col htmlFor="name"> | ||||||
|           {t('user.name')} |           {t('user.name')} | ||||||
|         </CLabel> |         </CLabel> | ||||||
|         <CCol sm="4"> |         <CCol lg="4" xxl="5"> | ||||||
|           <CInput id="name" value={user.name.value} onChange={updateUserWithId} maxLength="20" /> |           <CInput id="name" value={user.name.value} onChange={updateUserWithId} maxLength="20" /> | ||||||
|         </CCol> |         </CCol> | ||||||
|         <CLabel sm="2" col htmlFor="description"> |         <CLabel lg="2" xxl="1" col htmlFor="description"> | ||||||
|           {t('user.description')} |           {t('user.description')} | ||||||
|         </CLabel> |         </CLabel> | ||||||
|         <CCol sm="4"> |         <CCol lg="4" xxl="5"> | ||||||
|           <CInput |           <CInput | ||||||
|             id="description" |             id="description" | ||||||
|             value={user.description.value} |             value={user.description.value} | ||||||
| @@ -62,11 +89,17 @@ const EditMyProfile = ({ | |||||||
|         </CCol> |         </CCol> | ||||||
|       </CFormGroup> |       </CFormGroup> | ||||||
|       <CFormGroup row> |       <CFormGroup row> | ||||||
|         <CLabel sm="2" col htmlFor="userRole"> |         <CLabel lg="2" xxl="1" col htmlFor="userRole"> | ||||||
|           {t('user.user_role')} |           {t('user.user_role')} | ||||||
|         </CLabel> |         </CLabel> | ||||||
|         <CCol sm="4"> |         <CCol lg="4" xxl="5"> | ||||||
|           <CSelect custom id="userRole" onChange={updateUserWithId} value={user.userRole.value}> |           <CSelect | ||||||
|  |             custom | ||||||
|  |             id="userRole" | ||||||
|  |             onChange={updateUserWithId} | ||||||
|  |             value={user.userRole.value} | ||||||
|  |             style={{ width: '100px' }} | ||||||
|  |           > | ||||||
|             <option value="admin">Admin</option> |             <option value="admin">Admin</option> | ||||||
|             <option value="csr">CSR</option> |             <option value="csr">CSR</option> | ||||||
|             <option value="root">Root</option> |             <option value="root">Root</option> | ||||||
| @@ -75,10 +108,10 @@ const EditMyProfile = ({ | |||||||
|             <option value="system">System</option> |             <option value="system">System</option> | ||||||
|           </CSelect> |           </CSelect> | ||||||
|         </CCol> |         </CCol> | ||||||
|         <CLabel sm="2" col htmlFor="currentPassword"> |         <CLabel lg="2" xxl="1" col htmlFor="currentPassword"> | ||||||
|           {t('login.new_password')} |           {t('login.new_password')} | ||||||
|         </CLabel> |         </CLabel> | ||||||
|         <CCol sm="4"> |         <CCol lg="4" xxl="5"> | ||||||
|           <CInputGroup> |           <CInputGroup> | ||||||
|             <CInput |             <CInput | ||||||
|               type={showPassword ? 'text' : 'password'} |               type={showPassword ? 'text' : 'password'} | ||||||
| @@ -103,25 +136,42 @@ const EditMyProfile = ({ | |||||||
|         </CCol> |         </CCol> | ||||||
|       </CFormGroup> |       </CFormGroup> | ||||||
|       <CFormGroup row> |       <CFormGroup row> | ||||||
|         <CCol sm="6"> |         <CLabel lg="2" xxl="1" col htmlFor="userRole"> | ||||||
|           <NotesTable |           {t('user.user_role')} | ||||||
|             t={t} |         </CLabel> | ||||||
|             notes={user.notes.value} |         <CCol lg="4" xxl="5"> | ||||||
|             addNote={addNote} |           <CSelect | ||||||
|             loading={loading} |             custom | ||||||
|             size="lg" |             id="mfaMethod" | ||||||
|           /> |             onChange={updateUserWithId} | ||||||
|  |             value={user.mfaMethod.value} | ||||||
|  |             style={{ width: '100px' }} | ||||||
|  |           > | ||||||
|  |             <option value="">Off</option> | ||||||
|  |             <option value="sms">SMS</option> | ||||||
|  |             <option value="email">Email</option> | ||||||
|  |           </CSelect> | ||||||
|         </CCol> |         </CCol> | ||||||
|         <CLabel sm="2" col htmlFor="avatar"> |         <CLabel lg="2" xxl="1" col htmlFor="name"> | ||||||
|  |           {t('user.phone_number')} | ||||||
|  |         </CLabel> | ||||||
|  |         <CCol lg="4" xxl="5"> | ||||||
|  |           <CButton color="link" onClick={togglePhoneModal} className="pl-0"> | ||||||
|  |             {parseNumber()} | ||||||
|  |           </CButton> | ||||||
|  |         </CCol> | ||||||
|  |       </CFormGroup> | ||||||
|  |       <CFormGroup row> | ||||||
|  |         <CLabel lg="2" xl="1" col htmlFor="avatar" className="pt-2"> | ||||||
|           {t('user.avatar')} |           {t('user.avatar')} | ||||||
|         </CLabel> |         </CLabel> | ||||||
|         <CCol sm="4"> |         <CCol lg="10" xl="5" className="pt-2"> | ||||||
|           <CRow> |           <CRow> | ||||||
|             <CCol sm="3" className="pt-2"> |             <CCol lg="2" xl="2" className="pt-2"> | ||||||
|               {t('common.current')} |               {t('common.current')} | ||||||
|               <div className="pt-5">Preview</div> |               <div className="pt-5">Preview</div> | ||||||
|             </CCol> |             </CCol> | ||||||
|             <CCol sm="2"> |             <CCol lg="1" xl="1"> | ||||||
|               <Avatar src={avatar} fallback={user.email.value} /> |               <Avatar src={avatar} fallback={user.email.value} /> | ||||||
|               <div className="pt-3"> |               <div className="pt-3"> | ||||||
|                 <Avatar src={newAvatar} fallback={user.email.value} /> |                 <Avatar src={newAvatar} fallback={user.email.value} /> | ||||||
| @@ -150,6 +200,15 @@ const EditMyProfile = ({ | |||||||
|             </CCol> |             </CCol> | ||||||
|           </CRow> |           </CRow> | ||||||
|         </CCol> |         </CCol> | ||||||
|  |         <CCol lg="12" xl="6" className="pt-2"> | ||||||
|  |           <NotesTable | ||||||
|  |             t={t} | ||||||
|  |             notes={user.notes.value} | ||||||
|  |             addNote={addNote} | ||||||
|  |             loading={loading} | ||||||
|  |             size="lg" | ||||||
|  |           /> | ||||||
|  |         </CCol> | ||||||
|       </CFormGroup> |       </CFormGroup> | ||||||
|       <CRow> |       <CRow> | ||||||
|         <CCol /> |         <CCol /> | ||||||
| @@ -165,6 +224,14 @@ const EditMyProfile = ({ | |||||||
|           </CLink> |           </CLink> | ||||||
|         </CCol> |         </CCol> | ||||||
|       </CRow> |       </CRow> | ||||||
|  |       <ValidatePhoneNumberModal | ||||||
|  |         t={t} | ||||||
|  |         show={showPhoneModal} | ||||||
|  |         toggle={togglePhoneModal} | ||||||
|  |         sendPhoneNumberTest={sendPhoneNumberTest} | ||||||
|  |         save={saveNumber} | ||||||
|  |         testVerificationCode={testVerificationCode} | ||||||
|  |       /> | ||||||
|     </CForm> |     </CForm> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| @@ -181,6 +248,9 @@ EditMyProfile.propTypes = { | |||||||
|   showPreview: PropTypes.func.isRequired, |   showPreview: PropTypes.func.isRequired, | ||||||
|   deleteAvatar: PropTypes.func.isRequired, |   deleteAvatar: PropTypes.func.isRequired, | ||||||
|   fileInputKey: PropTypes.number.isRequired, |   fileInputKey: PropTypes.number.isRequired, | ||||||
|  |   sendPhoneNumberTest: PropTypes.func.isRequired, | ||||||
|  |   testVerificationCode: PropTypes.func.isRequired, | ||||||
|  |   updateWithKey: PropTypes.func.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| EditMyProfile.defaultProps = { | EditMyProfile.defaultProps = { | ||||||
|   | |||||||
| @@ -53,8 +53,11 @@ const EditUserForm = ({ t, user, updateUserWithId, loading, policies, addNote }) | |||||||
|         </CLabel> |         </CLabel> | ||||||
|         <CCol sm="4"> |         <CCol sm="4"> | ||||||
|           <CSelect custom id="userRole" onChange={updateUserWithId} value={user.userRole.value}> |           <CSelect custom id="userRole" onChange={updateUserWithId} value={user.userRole.value}> | ||||||
|  |             <option value="accounting">Accounting</option> | ||||||
|             <option value="admin">Admin</option> |             <option value="admin">Admin</option> | ||||||
|             <option value="csr">CSR</option> |             <option value="csr">CSR</option> | ||||||
|  |             <option value="installer">Installer</option> | ||||||
|  |             <option value="noc">NOC</option> | ||||||
|             <option value="root">Root</option> |             <option value="root">Root</option> | ||||||
|             <option value="special">Special</option> |             <option value="special">Special</option> | ||||||
|             <option value="sub">Sub</option> |             <option value="sub">Sub</option> | ||||||
|   | |||||||
							
								
								
									
										134
									
								
								src/components/FileToStringButton/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/components/FileToStringButton/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | |||||||
|  | import React, { useEffect, useState } from 'react'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { cilCloudUpload, cilSave, cilTrash, cilX } from '@coreui/icons'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { | ||||||
|  |   CButton, | ||||||
|  |   CPopover, | ||||||
|  |   CModal, | ||||||
|  |   CModalBody, | ||||||
|  |   CModalHeader, | ||||||
|  |   CModalTitle, | ||||||
|  |   CInputFile, | ||||||
|  |   CAlert, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import useToggle from '../../hooks/useToggle'; | ||||||
|  |  | ||||||
|  | const validatePem = (value) => | ||||||
|  |   (value.includes('---BEGIN CERTIFICATE---') && value.includes('---END CERTIFICATE---')) || | ||||||
|  |   (value.includes('---BEGIN PRIVATE KEY---') && value.includes('---END PRIVATE KEY---')); | ||||||
|  |  | ||||||
|  | const FileToStringButton = ({ t, save, title, explanations, acceptedFileTypes, size }) => { | ||||||
|  |   const [show, toggle] = useToggle(false); | ||||||
|  |   const [value, setValue] = useState(''); | ||||||
|  |   const [fileName, setFileName] = useState(''); | ||||||
|  |   const [fileError, setFileError] = useState(false); | ||||||
|  |   const [key, setKey] = useState(0); | ||||||
|  |  | ||||||
|  |   let fileReader; | ||||||
|  |  | ||||||
|  |   const saveValue = () => { | ||||||
|  |     save(value, fileName); | ||||||
|  |     toggle(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const deleteValue = () => { | ||||||
|  |     save('', ''); | ||||||
|  |     toggle(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const clear = () => { | ||||||
|  |     setValue(''); | ||||||
|  |     setFileError(false); | ||||||
|  |     setFileName(''); | ||||||
|  |     setKey(key + 1); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleFileRead = () => { | ||||||
|  |     setFileError(false); | ||||||
|  |     const content = fileReader.result; | ||||||
|  |     if (content && validatePem(content)) { | ||||||
|  |       setValue(window.btoa(content)); | ||||||
|  |     } else { | ||||||
|  |       setFileError(true); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleFile = (file) => { | ||||||
|  |     fileReader = new FileReader(); | ||||||
|  |     if (file?.name !== undefined) setFileName(file.name); | ||||||
|  |     fileReader.onloadend = handleFileRead; | ||||||
|  |     fileReader.readAsText(file); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (show) clear(); | ||||||
|  |   }, [show]); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <div> | ||||||
|  |       <CPopover content="Import from PEM file"> | ||||||
|  |         <CButton | ||||||
|  |           onClick={toggle} | ||||||
|  |           color="primary" | ||||||
|  |           variant="outline" | ||||||
|  |           style={{ height: '35px', width: '35px' }} | ||||||
|  |           size={size} | ||||||
|  |         > | ||||||
|  |           <CIcon content={cilCloudUpload} /> | ||||||
|  |         </CButton> | ||||||
|  |       </CPopover> | ||||||
|  |       <CModal size="lg" show={show} onClose={toggle}> | ||||||
|  |         <CModalHeader className="p-1"> | ||||||
|  |           <CModalTitle className="pl-1 pt-1">{title}</CModalTitle> | ||||||
|  |           <div className="text-right"> | ||||||
|  |             <CPopover content={t('common.save')}> | ||||||
|  |               <CButton color="primary" variant="outline" className="ml-2" onClick={saveValue}> | ||||||
|  |                 <CIcon content={cilSave} /> | ||||||
|  |               </CButton> | ||||||
|  |             </CPopover> | ||||||
|  |             <CPopover content={t('common.delete')}> | ||||||
|  |               <CButton color="primary" variant="outline" className="ml-2" onClick={deleteValue}> | ||||||
|  |                 <CIcon content={cilTrash} /> | ||||||
|  |               </CButton> | ||||||
|  |             </CPopover> | ||||||
|  |             <CPopover content={t('common.close')}> | ||||||
|  |               <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}> | ||||||
|  |                 <CIcon content={cilX} /> | ||||||
|  |               </CButton> | ||||||
|  |             </CPopover> | ||||||
|  |           </div> | ||||||
|  |         </CModalHeader> | ||||||
|  |         <CModalBody> | ||||||
|  |           <h6>{explanations}</h6> | ||||||
|  |           <CInputFile | ||||||
|  |             className="my-3" | ||||||
|  |             key={key} | ||||||
|  |             id="file-input" | ||||||
|  |             name="file-input" | ||||||
|  |             accept={acceptedFileTypes} | ||||||
|  |             onChange={(e) => handleFile(e.target.files[0])} | ||||||
|  |           /> | ||||||
|  |           <CAlert className="my-3" hidden={!fileError} color="danger"> | ||||||
|  |             {acceptedFileTypes === '.pem' ? t('common.invalid_pem') : t('common.invalid_file')} | ||||||
|  |           </CAlert> | ||||||
|  |         </CModalBody> | ||||||
|  |       </CModal> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | FileToStringButton.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   save: PropTypes.func.isRequired, | ||||||
|  |   title: PropTypes.string.isRequired, | ||||||
|  |   explanations: PropTypes.string.isRequired, | ||||||
|  |   acceptedFileTypes: PropTypes.string.isRequired, | ||||||
|  |   size: PropTypes.string, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | FileToStringButton.defaultProps = { | ||||||
|  |   size: 'md', | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default React.memo(FileToStringButton); | ||||||
| @@ -1,8 +1,8 @@ | |||||||
| import React, { useEffect } from 'react'; | import React, { useEffect, useState } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import ReactPaginate from 'react-paginate'; | import ReactPaginate from 'react-paginate'; | ||||||
| import { CButton, CDataTable, CLink, CPopover, CButtonToolbar, CSelect } from '@coreui/react'; | import { CButton, CDataTable, CLink, CPopover, CButtonToolbar, CSelect } from '@coreui/react'; | ||||||
| import { cilPencil, cilPlus, cilSpreadsheet } from '@coreui/icons'; | import { cilPencil, cilPlus, cilRouter, cilSpreadsheet } from '@coreui/icons'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import ReactTooltip from 'react-tooltip'; | import ReactTooltip from 'react-tooltip'; | ||||||
| import DeleteButton from './DeleteButton'; | import DeleteButton from './DeleteButton'; | ||||||
| @@ -28,7 +28,9 @@ const InventoryTable = ({ | |||||||
|   toggleAssociate, |   toggleAssociate, | ||||||
|   toggleAssocEntity, |   toggleAssocEntity, | ||||||
|   toggleComputed, |   toggleComputed, | ||||||
|  |   pushConfig, | ||||||
| }) => { | }) => { | ||||||
|  |   const [gwUi] = useState(localStorage.getItem('owgw-ui')); | ||||||
|   const columns = |   const columns = | ||||||
|     onlyEntity || onlyUnassigned |     onlyEntity || onlyUnassigned | ||||||
|       ? [ |       ? [ | ||||||
| @@ -75,7 +77,19 @@ const InventoryTable = ({ | |||||||
|         border |         border | ||||||
|         loading={loading} |         loading={loading} | ||||||
|         scopedSlots={{ |         scopedSlots={{ | ||||||
|           serialNumber: (item) => <td className="align-middle">{item.serialNumber}</td>, |           serialNumber: (item) => ( | ||||||
|  |             <td className="align-middle"> | ||||||
|  |               <CLink | ||||||
|  |                 className="c-subheader-nav-link align-self-center" | ||||||
|  |                 aria-current="page" | ||||||
|  |                 href={`${gwUi}/#/devices/${item.serialNumber}`} | ||||||
|  |                 target="_blank" | ||||||
|  |                 disabled={!gwUi || gwUi === ''} | ||||||
|  |               > | ||||||
|  |                 {item.serialNumber} | ||||||
|  |               </CLink> | ||||||
|  |             </td> | ||||||
|  |           ), | ||||||
|           name: (item) => <td className="align-middle">{item.name}</td>, |           name: (item) => <td className="align-middle">{item.name}</td>, | ||||||
|           created: (item) => ( |           created: (item) => ( | ||||||
|             <td className="align-middle"> |             <td className="align-middle"> | ||||||
| @@ -97,9 +111,9 @@ const InventoryTable = ({ | |||||||
|                   }) |                   }) | ||||||
|                 } |                 } | ||||||
|               > |               > | ||||||
|                 {item.deviceConfigurationName === '' |                 {item.deviceConfiguration === '' | ||||||
|                   ? t('configuration.add_or_link') |                   ? t('configuration.add_or_link') | ||||||
|                   : item.deviceConfigurationName} |                   : item.extendedInfo?.deviceConfiguration?.name ?? item.deviceConfiguration} | ||||||
|               </CButton> |               </CButton> | ||||||
|             </td> |             </td> | ||||||
|           ), |           ), | ||||||
| @@ -113,7 +127,7 @@ const InventoryTable = ({ | |||||||
|                   aria-current="page" |                   aria-current="page" | ||||||
|                   to={() => `/entity/${item.entity}`} |                   to={() => `/entity/${item.entity}`} | ||||||
|                 > |                 > | ||||||
|                   {item.entity_info ? item.entity_info.name : item.entity} |                   {item.extendedInfo?.entity?.name ?? item.entity} | ||||||
|                 </CLink> |                 </CLink> | ||||||
|               )} |               )} | ||||||
|             </td> |             </td> | ||||||
| @@ -128,7 +142,7 @@ const InventoryTable = ({ | |||||||
|                   aria-current="page" |                   aria-current="page" | ||||||
|                   to={() => `/venue/${item.venue}`} |                   to={() => `/venue/${item.venue}`} | ||||||
|                 > |                 > | ||||||
|                   {item.venue_info ? item.venue_info.name : item.venue} |                   {item.extendedInfo?.venue?.name ?? item.venue} | ||||||
|                 </CLink> |                 </CLink> | ||||||
|               )} |               )} | ||||||
|             </td> |             </td> | ||||||
| @@ -138,7 +152,7 @@ const InventoryTable = ({ | |||||||
|               <CButtonToolbar |               <CButtonToolbar | ||||||
|                 role="group" |                 role="group" | ||||||
|                 className="justify-content-flex-end" |                 className="justify-content-flex-end" | ||||||
|                 style={{ width: '250px' }} |                 style={{ width: '300px' }} | ||||||
|               > |               > | ||||||
|                 <CPopover content={t('inventory.assign_ent_ven')}> |                 <CPopover content={t('inventory.assign_ent_ven')}> | ||||||
|                   <div> |                   <div> | ||||||
| @@ -167,6 +181,20 @@ const InventoryTable = ({ | |||||||
|                   hideTooltips={hideTooltips} |                   hideTooltips={hideTooltips} | ||||||
|                 /> |                 /> | ||||||
|                 <DeleteButton t={t} tag={item} deleteTag={deleteTag} hideTooltips={hideTooltips} /> |                 <DeleteButton t={t} tag={item} deleteTag={deleteTag} hideTooltips={hideTooltips} /> | ||||||
|  |                 <CPopover content="Push Configuration to Device"> | ||||||
|  |                   <CButton | ||||||
|  |                     color="primary" | ||||||
|  |                     variant="outline" | ||||||
|  |                     shape="square" | ||||||
|  |                     size="sm" | ||||||
|  |                     className="mx-2" | ||||||
|  |                     onClick={() => pushConfig(item.serialNumber)} | ||||||
|  |                     style={{ width: '33px', height: '30px' }} | ||||||
|  |                     disabled={item.deviceConfigurationName === ''} | ||||||
|  |                   > | ||||||
|  |                     <CIcon name="cil-router" content={cilRouter} size="sm" /> | ||||||
|  |                   </CButton> | ||||||
|  |                 </CPopover> | ||||||
|                 <CPopover content="See Computed Configuration"> |                 <CPopover content="See Computed Configuration"> | ||||||
|                   <CButton |                   <CButton | ||||||
|                     color="primary" |                     color="primary" | ||||||
| @@ -255,6 +283,7 @@ InventoryTable.propTypes = { | |||||||
|   toggleAssociate: PropTypes.func.isRequired, |   toggleAssociate: PropTypes.func.isRequired, | ||||||
|   toggleAssocEntity: PropTypes.func.isRequired, |   toggleAssocEntity: PropTypes.func.isRequired, | ||||||
|   toggleComputed: PropTypes.func.isRequired, |   toggleComputed: PropTypes.func.isRequired, | ||||||
|  |   pushConfig: PropTypes.func.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| InventoryTable.defaultProps = { | InventoryTable.defaultProps = { | ||||||
|   | |||||||
							
								
								
									
										114
									
								
								src/components/LoginPage/AccountVerificationForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/components/LoginPage/AccountVerificationForm.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
|  | import { CButton, CCol, CForm, CInput, CRow, CSpinner, CAlert, CLink } from '@coreui/react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import LanguageSwitcher from '../LanguageSwitcher'; | ||||||
|  | import styles from './index.module.scss'; | ||||||
|  |  | ||||||
|  | const AccountVerificationForm = ({ | ||||||
|  |   t, | ||||||
|  |   i18n, | ||||||
|  |   onKeyDown, | ||||||
|  |   toggleForgotPassword, | ||||||
|  |   validateVerificationCode, | ||||||
|  |   resendValidationCode, | ||||||
|  |   policies, | ||||||
|  |   formType, | ||||||
|  | }) => { | ||||||
|  |   const [sending, setSending] = useState(false); | ||||||
|  |   const [validating, setValidating] = useState(false); | ||||||
|  |   const [code, setCode] = useState(''); | ||||||
|  |   const [success, setSuccess] = useState(null); | ||||||
|  |  | ||||||
|  |   const onChange = (e) => setCode(e.target.value); | ||||||
|  |  | ||||||
|  |   const resendCode = () => { | ||||||
|  |     setSending(true); | ||||||
|  |  | ||||||
|  |     resendValidationCode().finally(() => setSending(false)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const validate = () => { | ||||||
|  |     setValidating(true); | ||||||
|  |     validateVerificationCode(code) | ||||||
|  |       .then((result) => setSuccess(result)) | ||||||
|  |       .finally(() => setValidating(false)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CForm onKeyDown={(e) => onKeyDown(e, validateVerificationCode)}> | ||||||
|  |       <h1> | ||||||
|  |         {t('login.account_verification')} | ||||||
|  |         <div className={styles.languageSwitcher}> | ||||||
|  |           <LanguageSwitcher i18n={i18n} /> | ||||||
|  |         </div> | ||||||
|  |       </h1> | ||||||
|  |       <p className="text-muted"> | ||||||
|  |         {formType.split('-')[1] === 'sms' | ||||||
|  |           ? t('login.phone_validation_explanation') | ||||||
|  |           : t('login.email_code_validation')} | ||||||
|  |       </p> | ||||||
|  |       <div className="d-flex flex-row"> | ||||||
|  |         <CInput | ||||||
|  |           autoFocus | ||||||
|  |           required | ||||||
|  |           type="text" | ||||||
|  |           value={code} | ||||||
|  |           placeholder={t('login.verification_code')} | ||||||
|  |           onChange={onChange} | ||||||
|  |         /> | ||||||
|  |         <CButton | ||||||
|  |           className="ml-4" | ||||||
|  |           style={{ width: '300px' }} | ||||||
|  |           color="primary" | ||||||
|  |           onClick={resendCode} | ||||||
|  |           disabled={sending || validating} | ||||||
|  |         > | ||||||
|  |           {sending ? t('login.sending_ellipsis') : t('user.send_code_again')} | ||||||
|  |           <CSpinner hidden={!sending} color="light" component="span" size="sm" /> | ||||||
|  |         </CButton> | ||||||
|  |       </div> | ||||||
|  |       <CRow className="pt-2"> | ||||||
|  |         <CCol> | ||||||
|  |           <CAlert show={success !== null} color={success ? 'success' : 'danger'}> | ||||||
|  |             {t('login.wrong_code')} | ||||||
|  |           </CAlert> | ||||||
|  |         </CCol> | ||||||
|  |       </CRow> | ||||||
|  |       <CRow className="pt-2"> | ||||||
|  |         <CCol> | ||||||
|  |           <CButton color="primary" className="px-4" onClick={validate} disabled={validating}> | ||||||
|  |             {validating ? t('login.sending_ellipsis') : t('user.validate_phone')} | ||||||
|  |             <CSpinner hidden={!validating} color="light" component="span" size="sm" /> | ||||||
|  |           </CButton> | ||||||
|  |           <CLink | ||||||
|  |             className="c-subheader-nav-link px-3" | ||||||
|  |             aria-current="page" | ||||||
|  |             href={policies.accessPolicy} | ||||||
|  |             target="_blank" | ||||||
|  |             hidden={policies.accessPolicy.length === 0} | ||||||
|  |           > | ||||||
|  |             {t('common.access_policy')} | ||||||
|  |           </CLink> | ||||||
|  |         </CCol> | ||||||
|  |         <CCol xs="5" className={styles.forgotPassword}> | ||||||
|  |           <CButton variant="ghost" color="primary" onClick={toggleForgotPassword}> | ||||||
|  |             {t('common.back_to_login')} | ||||||
|  |           </CButton> | ||||||
|  |         </CCol> | ||||||
|  |       </CRow> | ||||||
|  |     </CForm> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | AccountVerificationForm.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   i18n: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   onKeyDown: PropTypes.func.isRequired, | ||||||
|  |   toggleForgotPassword: PropTypes.func.isRequired, | ||||||
|  |   validateVerificationCode: PropTypes.func.isRequired, | ||||||
|  |   policies: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   resendValidationCode: PropTypes.func.isRequired, | ||||||
|  |   formType: PropTypes.string.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default React.memo(AccountVerificationForm); | ||||||
| @@ -106,37 +106,33 @@ const LoginForm = ({ | |||||||
|         </CAlert> |         </CAlert> | ||||||
|       </CCol> |       </CCol> | ||||||
|     </CRow> |     </CRow> | ||||||
|     <CRow> |     <div className="d-flex flex-row align-middle"> | ||||||
|       <CCol> |       <CButton color="primary" className="px-4" onClick={signIn} disabled={loading}> | ||||||
|         <CButton color="primary" className="px-4" onClick={signIn} disabled={loading}> |         {loading ? t('login.logging_in') : t('login.login')} | ||||||
|           {loading ? t('login.logging_in') : t('login.login')} |         <CSpinner hidden={!loading} color="light" component="span" size="sm" /> | ||||||
|           <CSpinner hidden={!loading} color="light" component="span" size="sm" /> |       </CButton> | ||||||
|         </CButton> |       <CLink | ||||||
|         <CLink |         className="c-subheader-nav-link px-3 align-self-center" | ||||||
|           className="c-subheader-nav-link px-3" |         aria-current="page" | ||||||
|           aria-current="page" |         href={policies.accessPolicy} | ||||||
|           href={policies.accessPolicy} |         target="_blank" | ||||||
|           target="_blank" |         hidden={policies.accessPolicy.length === 0} | ||||||
|           hidden={policies.accessPolicy.length === 0} |       > | ||||||
|         > |         {t('common.access_policy')} | ||||||
|           {t('common.access_policy')} |       </CLink> | ||||||
|         </CLink> |       <CLink | ||||||
|         <CLink |         className="c-subheader-nav-link align-self-center" | ||||||
|           className="c-subheader-nav-link" |         aria-current="page" | ||||||
|           aria-current="page" |         href={policies.passwordPolicy} | ||||||
|           href={policies.passwordPolicy} |         target="_blank" | ||||||
|           target="_blank" |         hidden={policies.passwordPolicy.length === 0} | ||||||
|           hidden={policies.passwordPolicy.length === 0} |       > | ||||||
|         > |         {t('common.password_policy')} | ||||||
|           {t('common.password_policy')} |       </CLink> | ||||||
|         </CLink> |       <CButton className="ml-auto" variant="ghost" color="primary" onClick={toggleForgotPassword}> | ||||||
|       </CCol> |         {t('common.forgot_password')} | ||||||
|       <CCol xs="5" className="text-right"> |       </CButton> | ||||||
|         <CButton variant="ghost" color="primary" onClick={toggleForgotPassword}> |     </div> | ||||||
|           {t('common.forgot_password')} |  | ||||||
|         </CButton> |  | ||||||
|       </CCol> |  | ||||||
|     </CRow> |  | ||||||
|   </CForm> |   </CForm> | ||||||
| ); | ); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import styles from './index.module.scss'; | |||||||
| import LoginForm from './LoginForm'; | import LoginForm from './LoginForm'; | ||||||
| import ForgotPasswordForm from './ForgotPasswordForm'; | import ForgotPasswordForm from './ForgotPasswordForm'; | ||||||
| import ChangePasswordForm from './ChangePasswordForm'; | import ChangePasswordForm from './ChangePasswordForm'; | ||||||
|  | import AccountVerificationForm from './AccountVerificationForm'; | ||||||
|  |  | ||||||
| const LoginPage = ({ | const LoginPage = ({ | ||||||
|   t, |   t, | ||||||
| @@ -16,17 +17,32 @@ const LoginPage = ({ | |||||||
|   forgotResponse, |   forgotResponse, | ||||||
|   fields, |   fields, | ||||||
|   updateField, |   updateField, | ||||||
|   isLogin, |   formType, | ||||||
|   isPasswordChange, |  | ||||||
|   toggleForgotPassword, |   toggleForgotPassword, | ||||||
|   onKeyDown, |   onKeyDown, | ||||||
|   sendForgotPasswordEmail, |   sendForgotPasswordEmail, | ||||||
|   changePasswordResponse, |   changePasswordResponse, | ||||||
|   cancelPasswordChange, |   cancelPasswordChange, | ||||||
|   policies, |   policies, | ||||||
|  |   validateCode, | ||||||
|  |   resendValidationCode, | ||||||
| }) => { | }) => { | ||||||
|   const getForm = () => { |   const getForm = () => { | ||||||
|     if (!isLogin) { |     if (formType.split('-')[0] === 'validation') { | ||||||
|  |       return ( | ||||||
|  |         <AccountVerificationForm | ||||||
|  |           t={t} | ||||||
|  |           i18n={i18n} | ||||||
|  |           onKeyDown={onKeyDown} | ||||||
|  |           toggleForgotPassword={toggleForgotPassword} | ||||||
|  |           validateVerificationCode={validateCode} | ||||||
|  |           resendValidationCode={resendValidationCode} | ||||||
|  |           policies={policies} | ||||||
|  |           formType={formType} | ||||||
|  |         /> | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     if (formType === 'forgot-password') { | ||||||
|       return ( |       return ( | ||||||
|         <ForgotPasswordForm |         <ForgotPasswordForm | ||||||
|           t={t} |           t={t} | ||||||
| @@ -43,7 +59,7 @@ const LoginPage = ({ | |||||||
|         /> |         /> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     if (isPasswordChange) { |     if (formType === 'change-password') { | ||||||
|       return ( |       return ( | ||||||
|         <ChangePasswordForm |         <ChangePasswordForm | ||||||
|           t={t} |           t={t} | ||||||
| @@ -86,7 +102,7 @@ const LoginPage = ({ | |||||||
|               alt="OpenWifi" |               alt="OpenWifi" | ||||||
|             /> |             /> | ||||||
|             <CCardGroup> |             <CCardGroup> | ||||||
|               <CCard className="p-4" color={isLogin && isPasswordChange ? 'secondary' : ''}> |               <CCard className="p-4" color={formType === 'change-password' ? 'secondary' : ''}> | ||||||
|                 <CCardBody>{getForm()}</CCardBody> |                 <CCardBody>{getForm()}</CCardBody> | ||||||
|               </CCard> |               </CCard> | ||||||
|             </CCardGroup> |             </CCardGroup> | ||||||
| @@ -107,14 +123,15 @@ LoginPage.propTypes = { | |||||||
|   forgotResponse: PropTypes.instanceOf(Object).isRequired, |   forgotResponse: PropTypes.instanceOf(Object).isRequired, | ||||||
|   fields: PropTypes.instanceOf(Object).isRequired, |   fields: PropTypes.instanceOf(Object).isRequired, | ||||||
|   updateField: PropTypes.func.isRequired, |   updateField: PropTypes.func.isRequired, | ||||||
|   isLogin: PropTypes.bool.isRequired, |  | ||||||
|   isPasswordChange: PropTypes.bool.isRequired, |  | ||||||
|   toggleForgotPassword: PropTypes.func.isRequired, |   toggleForgotPassword: PropTypes.func.isRequired, | ||||||
|   onKeyDown: PropTypes.func.isRequired, |   onKeyDown: PropTypes.func.isRequired, | ||||||
|   sendForgotPasswordEmail: PropTypes.func.isRequired, |   sendForgotPasswordEmail: PropTypes.func.isRequired, | ||||||
|   changePasswordResponse: PropTypes.instanceOf(Object).isRequired, |   changePasswordResponse: PropTypes.instanceOf(Object).isRequired, | ||||||
|   cancelPasswordChange: PropTypes.func.isRequired, |   cancelPasswordChange: PropTypes.func.isRequired, | ||||||
|   policies: PropTypes.instanceOf(Object).isRequired, |   policies: PropTypes.instanceOf(Object).isRequired, | ||||||
|  |   formType: PropTypes.string.isRequired, | ||||||
|  |   validateCode: PropTypes.func.isRequired, | ||||||
|  |   resendValidationCode: PropTypes.func.isRequired, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default React.memo(LoginPage); | export default React.memo(LoginPage); | ||||||
|   | |||||||
| @@ -12,9 +12,10 @@ import { | |||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
| import { cilBan, cilCheckCircle, cilPencil, cilPlus, cilSync, cilTrash } from '@coreui/icons'; | import { cilBan, cilCheckCircle, cilPencil, cilPlus, cilSync, cilTrash } from '@coreui/icons'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import { capitalizeFirstLetter, prettyDate } from '../../utils/formatting'; | import { capitalizeFirstLetter } from '../../utils/formatting'; | ||||||
| import DeleteModal from '../DeleteModal'; | import DeleteModal from '../DeleteModal'; | ||||||
| import Avatar from '../Avatar'; | import Avatar from '../Avatar'; | ||||||
|  | import FormattedDate from '../FormattedDate'; | ||||||
|  |  | ||||||
| const UserListTable = ({ | const UserListTable = ({ | ||||||
|   t, |   t, | ||||||
| @@ -115,7 +116,9 @@ const UserListTable = ({ | |||||||
|                 </td> |                 </td> | ||||||
|               ), |               ), | ||||||
|               lastLogin: (item) => ( |               lastLogin: (item) => ( | ||||||
|                 <td className="align-middle">{item.lastLogin ? prettyDate(item.lastLogin) : ''}</td> |                 <td className="align-middle"> | ||||||
|  |                   {item.lastLogin ? <FormattedDate date={item.lastLogin} /> : ''} | ||||||
|  |                 </td> | ||||||
|               ), |               ), | ||||||
|               validated: (item) => ( |               validated: (item) => ( | ||||||
|                 <td className="text-center align-middle"> |                 <td className="text-center align-middle"> | ||||||
|   | |||||||
							
								
								
									
										166
									
								
								src/components/ValidatePhoneNumberModal/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/components/ValidatePhoneNumberModal/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | |||||||
|  | import React, { useEffect, useState } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import PhoneInput from 'react-phone-input-2'; | ||||||
|  | import 'react-phone-input-2/lib/bootstrap.css'; | ||||||
|  | import parsePhoneNumber from 'libphonenumber-js'; | ||||||
|  | import { | ||||||
|  |   CModal, | ||||||
|  |   CModalBody, | ||||||
|  |   CModalHeader, | ||||||
|  |   CModalTitle, | ||||||
|  |   CPopover, | ||||||
|  |   CButton, | ||||||
|  |   CRow, | ||||||
|  |   CCol, | ||||||
|  |   CInput, | ||||||
|  |   CAlert, | ||||||
|  | } from '@coreui/react'; | ||||||
|  | import CIcon from '@coreui/icons-react'; | ||||||
|  | import { cilSave, cilX } from '@coreui/icons'; | ||||||
|  |  | ||||||
|  | const ValidatePhoneNumberModal = ({ | ||||||
|  |   t, | ||||||
|  |   show, | ||||||
|  |   toggle, | ||||||
|  |   save, | ||||||
|  |   sendPhoneNumberTest, | ||||||
|  |   testVerificationCode, | ||||||
|  | }) => { | ||||||
|  |   const [loading, setLoading] = useState(false); | ||||||
|  |   const [sent, setSent] = useState(false); | ||||||
|  |   const [phoneNumber, setPhoneNumber] = useState(null); | ||||||
|  |   const [code, setCode] = useState(''); | ||||||
|  |   const [phoneValidated, setPhoneValidated] = useState(false); | ||||||
|  |  | ||||||
|  |   const onCodeChange = (e) => { | ||||||
|  |     setPhoneValidated(false); | ||||||
|  |     setCode(e.target.value); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const sendTest = async () => { | ||||||
|  |     setLoading(true); | ||||||
|  |     setSent(false); | ||||||
|  |  | ||||||
|  |     sendPhoneNumberTest(`+${phoneNumber}`) | ||||||
|  |       .then((result) => setSent(result)) | ||||||
|  |       .finally(setLoading(false)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const testCode = async () => { | ||||||
|  |     testVerificationCode(`+${phoneNumber}`, code).then((result) => { | ||||||
|  |       setPhoneValidated(result); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const validateRawPhoneNumber = () => { | ||||||
|  |     if (phoneNumber !== null) { | ||||||
|  |       const numberTest = parsePhoneNumber(`+${phoneNumber}`); | ||||||
|  |       if (numberTest) { | ||||||
|  |         return numberTest.isValid(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const saveNumber = () => { | ||||||
|  |     toggle(); | ||||||
|  |     save(phoneNumber); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (show) { | ||||||
|  |       setPhoneNumber(null); | ||||||
|  |       setLoading(false); | ||||||
|  |       setSent(false); | ||||||
|  |       setCode(''); | ||||||
|  |       setPhoneValidated(false); | ||||||
|  |     } | ||||||
|  |   }, [show]); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <CModal show={show} onClose={toggle}> | ||||||
|  |       <CModalHeader className="p-1"> | ||||||
|  |         <CModalTitle className="pl-1 pt-1">{t('user.phone_number')}</CModalTitle> | ||||||
|  |         <div className="text-right"> | ||||||
|  |           <CPopover content={t('common.save')}> | ||||||
|  |             <CButton | ||||||
|  |               color="primary" | ||||||
|  |               variant="outline" | ||||||
|  |               className="ml-2" | ||||||
|  |               onClick={saveNumber} | ||||||
|  |               disabled={!phoneValidated} | ||||||
|  |             > | ||||||
|  |               <CIcon content={cilSave} /> | ||||||
|  |             </CButton> | ||||||
|  |           </CPopover> | ||||||
|  |           <CPopover content={t('common.close')}> | ||||||
|  |             <CButton color="primary" variant="outline" className="ml-2" onClick={toggle}> | ||||||
|  |               <CIcon content={cilX} /> | ||||||
|  |             </CButton> | ||||||
|  |           </CPopover> | ||||||
|  |         </div> | ||||||
|  |       </CModalHeader> | ||||||
|  |       <CModalBody> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol> | ||||||
|  |             <h6>{t('user.enter_new_phone')}</h6> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CRow> | ||||||
|  |           <CCol sm="8"> | ||||||
|  |             <PhoneInput | ||||||
|  |               enableSearch | ||||||
|  |               value={phoneNumber} | ||||||
|  |               onChange={setPhoneNumber} | ||||||
|  |               disabled={loading} | ||||||
|  |             /> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol> | ||||||
|  |             <CButton | ||||||
|  |               color="primary" | ||||||
|  |               onClick={sendTest} | ||||||
|  |               className="mt-1" | ||||||
|  |               disabled={!validateRawPhoneNumber() || loading} | ||||||
|  |             > | ||||||
|  |               {t('user.send_code')} | ||||||
|  |             </CButton> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <h6 hidden={!sent} className="pt-4"> | ||||||
|  |           {t('user.check_phone')} | ||||||
|  |         </h6> | ||||||
|  |         <CRow hidden={!sent}> | ||||||
|  |           <CCol sm="3"> | ||||||
|  |             <CInput | ||||||
|  |               style={{ width: '110px' }} | ||||||
|  |               type="text" | ||||||
|  |               value={code} | ||||||
|  |               onChange={onCodeChange} | ||||||
|  |               maxLength="6" | ||||||
|  |               placeholder="Enter code" | ||||||
|  |             /> | ||||||
|  |           </CCol> | ||||||
|  |           <CCol sm="3"> | ||||||
|  |             <CButton color="primary" onClick={testCode}> | ||||||
|  |               {t('user.validate_phone')} | ||||||
|  |             </CButton> | ||||||
|  |           </CCol> | ||||||
|  |         </CRow> | ||||||
|  |         <CAlert hidden={!phoneValidated} className="mt-4" color="success"> | ||||||
|  |           {t('user.successful_validation')} | ||||||
|  |         </CAlert> | ||||||
|  |       </CModalBody> | ||||||
|  |     </CModal> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ValidatePhoneNumberModal.propTypes = { | ||||||
|  |   t: PropTypes.func.isRequired, | ||||||
|  |   show: PropTypes.bool.isRequired, | ||||||
|  |   toggle: PropTypes.func.isRequired, | ||||||
|  |   save: PropTypes.func.isRequired, | ||||||
|  |   sendPhoneNumberTest: PropTypes.func.isRequired, | ||||||
|  |   testVerificationCode: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default ValidatePhoneNumberModal; | ||||||
| @@ -13,6 +13,7 @@ import { | |||||||
|   CLink, |   CLink, | ||||||
|   CPopover, |   CPopover, | ||||||
| } from '@coreui/react'; | } from '@coreui/react'; | ||||||
|  | import { useHistory } from 'react-router-dom'; | ||||||
| import { cilPencil, cilPlus, cilSync } from '@coreui/icons'; | import { cilPencil, cilPlus, cilSync } from '@coreui/icons'; | ||||||
| import CIcon from '@coreui/icons-react'; | import CIcon from '@coreui/icons-react'; | ||||||
| import DeleteButton from './DeleteButton'; | import DeleteButton from './DeleteButton'; | ||||||
| @@ -33,6 +34,8 @@ const VenueTable = ({ | |||||||
|   deleteVenue, |   deleteVenue, | ||||||
|   refresh, |   refresh, | ||||||
| }) => { | }) => { | ||||||
|  |   const history = useHistory(); | ||||||
|  |  | ||||||
|   const columns = [ |   const columns = [ | ||||||
|     { key: 'name', label: t('user.name'), _style: { width: '20%' } }, |     { key: 'name', label: t('user.name'), _style: { width: '20%' } }, | ||||||
|     { key: 'description', label: t('user.description'), _style: { width: '25%' } }, |     { key: 'description', label: t('user.description'), _style: { width: '25%' } }, | ||||||
| @@ -121,13 +124,12 @@ const VenueTable = ({ | |||||||
|                   > |                   > | ||||||
|                     <CPopover content="Edit Tag"> |                     <CPopover content="Edit Tag"> | ||||||
|                       <CButton |                       <CButton | ||||||
|                         disabled |  | ||||||
|                         color="primary" |                         color="primary" | ||||||
|                         variant="outline" |                         variant="outline" | ||||||
|                         shape="square" |                         shape="square" | ||||||
|                         size="sm" |                         size="sm" | ||||||
|                         className="mx-1" |                         className="mx-1" | ||||||
|                         onClick={() => {}} |                         onClick={() => history.push(`/venue/${item.id}`)} | ||||||
|                         style={{ width: '33px', height: '30px' }} |                         style={{ width: '33px', height: '30px' }} | ||||||
|                       > |                       > | ||||||
|                         <CIcon name="cil-pencil" content={cilPencil} size="sm" /> |                         <CIcon name="cil-pencil" content={cilPencil} size="sm" /> | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ export const AuthProvider = ({ axiosInstance, token, apiEndpoints, children }) = | |||||||
|       .then(() => {}) |       .then(() => {}) | ||||||
|       .catch(() => {}) |       .catch(() => {}) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|  |         localStorage.removeItem('access_token'); | ||||||
|  |         localStorage.removeItem('gateway_endpoints'); | ||||||
|         sessionStorage.clear(); |         sessionStorage.clear(); | ||||||
|         window.location.replace('/'); |         window.location.replace('/'); | ||||||
|       }); |       }); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| import React, { useEffect, useState } from 'react'; | import React, { useEffect, useState } from 'react'; | ||||||
| import { useHistory } from 'react-router-dom'; | import { useHistory } from 'react-router-dom'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
|  | import { v4 as createUuid } from 'uuid'; | ||||||
| import { set as lodashSet, get as lodashGet } from 'lodash'; | import { set as lodashSet, get as lodashGet } from 'lodash'; | ||||||
| import { useAuth } from '../AuthProvider'; | import { useAuth } from '../AuthProvider'; | ||||||
|  |  | ||||||
| @@ -14,6 +15,7 @@ const navbarOption = ({ | |||||||
|   children, |   children, | ||||||
|   childrenEntities, |   childrenEntities, | ||||||
|   childrenVenues, |   childrenVenues, | ||||||
|  |   extraData = {}, | ||||||
| }) => { | }) => { | ||||||
|   let tag = 'SidebarChildless'; |   let tag = 'SidebarChildless'; | ||||||
|   if (children) tag = 'SidebarDropdown'; |   if (children) tag = 'SidebarDropdown'; | ||||||
| @@ -25,7 +27,8 @@ const navbarOption = ({ | |||||||
|     name, |     name, | ||||||
|     path, |     path, | ||||||
|     isVenue, |     isVenue, | ||||||
|     onClick: () => selectEntity(uuid, name, path, isVenue, childrenEntities, childrenVenues), |     onClick: () => | ||||||
|  |       selectEntity(uuid, name, path, isVenue, childrenEntities, childrenVenues, extraData), | ||||||
|     _children: children, |     _children: children, | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| @@ -42,7 +45,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | |||||||
|   const [parentsWithChildrenLoaded, setParentsWithChildrenLoaded] = useState([]); |   const [parentsWithChildrenLoaded, setParentsWithChildrenLoaded] = useState([]); | ||||||
|   const [deviceTypes, setDeviceTypes] = useState([]); |   const [deviceTypes, setDeviceTypes] = useState([]); | ||||||
|  |  | ||||||
|   const selectEntity = (uuid, name, path, isVenue, childrenEntities, childrenVenues) => { |   const selectEntity = (uuid, name, path, isVenue, childrenEntities, childrenVenues, extraData) => { | ||||||
|     // If we have not yet gotten the information of this entity's children, we get them now |     // If we have not yet gotten the information of this entity's children, we get them now | ||||||
|     if (childrenEntities || childrenVenues) { |     if (childrenEntities || childrenVenues) { | ||||||
|       setEntityToRetrieve({ childrenEntities, childrenVenues, path, uuid, isVenue }); |       setEntityToRetrieve({ childrenEntities, childrenVenues, path, uuid, isVenue }); | ||||||
| @@ -54,6 +57,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | |||||||
|       childrenEntities, |       childrenEntities, | ||||||
|       childrenVenues, |       childrenVenues, | ||||||
|       path, |       path, | ||||||
|  |       extraData, | ||||||
|     }); |     }); | ||||||
|     history.push(`/${isVenue ? 'venue' : 'entity'}/${uuid}`); |     history.push(`/${isVenue ? 'venue' : 'entity'}/${uuid}`); | ||||||
|   }; |   }; | ||||||
| @@ -98,7 +102,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | |||||||
|   const setProviderEntity = async (id, isVenue) => { |   const setProviderEntity = async (id, isVenue) => { | ||||||
|     const newEntity = await getEntity(id, isVenue); |     const newEntity = await getEntity(id, isVenue); | ||||||
|     if (newEntity) { |     if (newEntity) { | ||||||
|       setEntity({ |       const newObj = { | ||||||
|         ...newEntity, |         ...newEntity, | ||||||
|         uuid: newEntity.id, |         uuid: newEntity.id, | ||||||
|         name: newEntity.name, |         name: newEntity.name, | ||||||
| @@ -106,7 +110,11 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | |||||||
|         isVenue, |         isVenue, | ||||||
|         childrenIds: newEntity.children, |         childrenIds: newEntity.children, | ||||||
|         childrenVenues: newEntity.venues, |         childrenVenues: newEntity.venues, | ||||||
|       }); |         extraData: newEntity, | ||||||
|  |         refreshId: createUuid(), | ||||||
|  |       }; | ||||||
|  |  | ||||||
|  |       setEntity({ ...newObj }); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -162,6 +170,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | |||||||
|           children: nestedOptions, |           children: nestedOptions, | ||||||
|           childrenEntities: grandChildrenEntities, |           childrenEntities: grandChildrenEntities, | ||||||
|           childrenVenues: grandChildrenVenues, |           childrenVenues: grandChildrenVenues, | ||||||
|  |           extraData: result, | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,11 @@ const hasError = (e, field) => { | |||||||
|       return `This field has a minimum value of ${field.minimum}`; |       return `This field has a minimum value of ${field.minimum}`; | ||||||
|     if (field.maximum !== undefined && e.target.value > field.maximum) |     if (field.maximum !== undefined && e.target.value > field.maximum) | ||||||
|       return `This field has a maximum value of ${field.maximum}`; |       return `This field has a maximum value of ${field.maximum}`; | ||||||
|  |   } else if (field.type && field.type === 'string') { | ||||||
|  |     if (field.minLength && e.target.value.length < field.minLength) | ||||||
|  |       return `This field has a minimum length of ${field.minLength}`; | ||||||
|  |     if (field.maxLength && e.target.value.length > field.maxLength) | ||||||
|  |       return `This field has a maximum length of ${field.maxLength}`; | ||||||
|   } |   } | ||||||
|   return null; |   return null; | ||||||
| }; | }; | ||||||
| @@ -44,5 +49,15 @@ export default (initialState) => { | |||||||
|         setFields({ ...formFields }); |         setFields({ ...formFields }); | ||||||
|       } else if (force) setFields({ ...formFields }); |       } else if (force) setFields({ ...formFields }); | ||||||
|     }, |     }, | ||||||
|  |     (fieldsList) => { | ||||||
|  |       const newFields = { ...fields }; | ||||||
|  |  | ||||||
|  |       fieldsList.forEach((field) => { | ||||||
|  |         const oldField = lodashGet(newFields, field.id); | ||||||
|  |         lodashSet(newFields, field.id, { ...oldField, value: field.value }); | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       setFields({ ...newFields }); | ||||||
|  |     }, | ||||||
|   ]; |   ]; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ export { default as Sidebar } from './layout/Sidebar'; | |||||||
|  |  | ||||||
| // Components | // Components | ||||||
| export { default as AddConfigurationForm } from './components/AddConfigurationForm'; | export { default as AddConfigurationForm } from './components/AddConfigurationForm'; | ||||||
|  | export { default as AddContactForm } from './components/AddContactForm'; | ||||||
| export { default as AddEntityForm } from './components/AddEntityForm'; | export { default as AddEntityForm } from './components/AddEntityForm'; | ||||||
| export { default as AddInventoryTagForm } from './components/AddInventoryTagForm'; | export { default as AddInventoryTagForm } from './components/AddInventoryTagForm'; | ||||||
| export { default as AddVenueForm } from './components/AddVenueForm'; | export { default as AddVenueForm } from './components/AddVenueForm'; | ||||||
| @@ -16,6 +17,7 @@ export { default as ApiStatusCard } from './components/ApiStatusCard'; | |||||||
| export { default as Avatar } from './components/Avatar'; | export { default as Avatar } from './components/Avatar'; | ||||||
| export { default as ConfigurationCustomMultiModal } from './components/Configuration/CustomMultiModal'; | export { default as ConfigurationCustomMultiModal } from './components/Configuration/CustomMultiModal'; | ||||||
| export { default as ConfigurationElement } from './components/Configuration/ConfigurationElement'; | export { default as ConfigurationElement } from './components/Configuration/ConfigurationElement'; | ||||||
|  | export { default as ConfigurationFileField } from './components/Configuration/FileField'; | ||||||
| export { default as ConfigurationInUseModal } from './components/ConfigurationInUseModal'; | export { default as ConfigurationInUseModal } from './components/ConfigurationInUseModal'; | ||||||
| export { default as ConfigurationIntField } from './components/Configuration/IntField'; | export { default as ConfigurationIntField } from './components/Configuration/IntField'; | ||||||
| export { default as ConfigurationMulti } from './components/Configuration/Multi'; | export { default as ConfigurationMulti } from './components/Configuration/Multi'; | ||||||
| @@ -25,6 +27,7 @@ export { default as ConfigurationSelect } from './components/Configuration/Selec | |||||||
| export { default as ConfigurationStringField } from './components/Configuration/StringField'; | export { default as ConfigurationStringField } from './components/Configuration/StringField'; | ||||||
| export { default as ConfigurationToggle } from './components/Configuration/Toggle'; | export { default as ConfigurationToggle } from './components/Configuration/Toggle'; | ||||||
| export { default as ConfirmFooter } from './components/ConfirmFooter'; | export { default as ConfirmFooter } from './components/ConfirmFooter'; | ||||||
|  | export { default as ContactTable } from './components/ContactTable'; | ||||||
| export { default as CopyToClipboardButton } from './components/CopyToClipboardButton'; | export { default as CopyToClipboardButton } from './components/CopyToClipboardButton'; | ||||||
| export { default as CreateUserForm } from './components/CreateUserForm'; | export { default as CreateUserForm } from './components/CreateUserForm'; | ||||||
| export { default as ConfigurationTable } from './components/ConfigurationTable'; | export { default as ConfigurationTable } from './components/ConfigurationTable'; | ||||||
| @@ -33,6 +36,7 @@ export { default as DeviceListTable } from './components/DeviceListTable'; | |||||||
| export { default as DeviceStatusCard } from './components/DeviceStatusCard'; | export { default as DeviceStatusCard } from './components/DeviceStatusCard'; | ||||||
| export { default as DeviceSearchBar } from './components/DeviceSearchBar'; | export { default as DeviceSearchBar } from './components/DeviceSearchBar'; | ||||||
| export { default as EditConfigurationForm } from './components/EditConfigurationForm'; | export { default as EditConfigurationForm } from './components/EditConfigurationForm'; | ||||||
|  | export { default as EditContactForm } from './components/EditContactForm'; | ||||||
| export { default as EditEntityForm } from './components/EditEntityForm'; | export { default as EditEntityForm } from './components/EditEntityForm'; | ||||||
| export { default as EditInventoryTagForm } from './components/EditInventoryTagForm'; | export { default as EditInventoryTagForm } from './components/EditInventoryTagForm'; | ||||||
| export { default as EditMyProfile } from './components/EditMyProfile'; | export { default as EditMyProfile } from './components/EditMyProfile'; | ||||||
| @@ -41,6 +45,7 @@ export { default as EditUserModal } from './components/EditUserModal'; | |||||||
| export { default as EditVenueForm } from './components/EditVenueForm'; | export { default as EditVenueForm } from './components/EditVenueForm'; | ||||||
| export { default as EventQueueModal } from './components/EventQueueModal'; | export { default as EventQueueModal } from './components/EventQueueModal'; | ||||||
| export { default as InventoryTable } from './components/InventoryTable'; | export { default as InventoryTable } from './components/InventoryTable'; | ||||||
|  | export { default as FileToStringButton } from './components/FileToStringButton'; | ||||||
| export { default as FirmwareHistoryTable } from './components/FirmwareHistoryTable'; | export { default as FirmwareHistoryTable } from './components/FirmwareHistoryTable'; | ||||||
| export { default as FirmwareList } from './components/FirmwareList'; | export { default as FirmwareList } from './components/FirmwareList'; | ||||||
| export { default as FormattedDate } from './components/FormattedDate'; | export { default as FormattedDate } from './components/FormattedDate'; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Charles
					Charles