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", | ||||
|   "version": "0.9.51", | ||||
|   "version": "0.9.71", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "ucentral-libs", | ||||
|       "version": "0.9.51", | ||||
|       "version": "0.9.71", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "@coreui/coreui": "^3.4.0", | ||||
| @@ -14,9 +14,11 @@ | ||||
|         "@coreui/icons-react": "^1.1.0", | ||||
|         "@coreui/react": "^3.4.6", | ||||
|         "@coreui/react-chartjs": "^1.1.0", | ||||
|         "libphonenumber-js": "^1.9.37", | ||||
|         "lodash": "^4.17.21", | ||||
|         "react-flow-renderer": "^9.6.6", | ||||
|         "react-paginate": "^7.1.3", | ||||
|         "react-phone-input-2": "^2.14.0", | ||||
|         "react-router-dom": "^5.2.0", | ||||
|         "react-select": "^4.3.1", | ||||
|         "react-tooltip": "^4.2.21", | ||||
| @@ -8278,6 +8280,11 @@ | ||||
|         "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": { | ||||
|       "version": "1.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", | ||||
| @@ -8518,8 +8525,12 @@ | ||||
|     "node_modules/lodash.debounce": { | ||||
|       "version": "4.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", | ||||
|       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", | ||||
|       "dev": true | ||||
|       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" | ||||
|     }, | ||||
|     "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": { | ||||
|       "version": "4.6.2", | ||||
| @@ -8527,6 +8538,16 @@ | ||||
|       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", | ||||
|       "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": { | ||||
|       "version": "4.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", | ||||
| @@ -10482,6 +10503,23 @@ | ||||
|         "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": { | ||||
|       "version": "7.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", | ||||
| @@ -20369,6 +20407,11 @@ | ||||
|         "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": { | ||||
|       "version": "1.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", | ||||
| @@ -20557,8 +20600,12 @@ | ||||
|     "lodash.debounce": { | ||||
|       "version": "4.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", | ||||
|       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", | ||||
|       "dev": true | ||||
|       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" | ||||
|     }, | ||||
|     "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": { | ||||
|       "version": "4.6.2", | ||||
| @@ -20566,6 +20613,16 @@ | ||||
|       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", | ||||
|       "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": { | ||||
|       "version": "4.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", | ||||
| @@ -22049,6 +22106,19 @@ | ||||
|         "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": { | ||||
|       "version": "7.2.4", | ||||
|       "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", | ||||
|   "version": "0.9.51", | ||||
|   "version": "0.9.71", | ||||
|   "main": "dist/index.js", | ||||
|   "source": "src/index.js", | ||||
|   "engines": { | ||||
| @@ -9,20 +9,22 @@ | ||||
|   "files": [ | ||||
|     "dist" | ||||
|   ], | ||||
|   "license" : "BSD-3-Clause", | ||||
|   "license": "BSD-3-Clause", | ||||
|   "dependencies": { | ||||
|     "@coreui/coreui": "^3.4.0", | ||||
|     "@coreui/icons": "^2.0.1", | ||||
|     "@coreui/icons-react": "^1.1.0", | ||||
|     "@coreui/react": "^3.4.6", | ||||
|     "@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-paginate": "^7.1.3", | ||||
|     "react-phone-input-2": "^2.14.0", | ||||
|     "react-router-dom": "^5.2.0", | ||||
|     "react-select": "^4.3.1", | ||||
|     "uuid": "^8.3.2", | ||||
|     "lodash": "^4.17.21" | ||||
|     "react-tooltip": "^4.2.21", | ||||
|     "uuid": "^8.3.2" | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "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 { | ||||
|   CFormGroup, | ||||
| @@ -13,7 +13,7 @@ import { | ||||
|   CDataTable, | ||||
| } from '@coreui/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'; | ||||
|  | ||||
| const CustomMultiModal = ({ | ||||
| @@ -28,15 +28,17 @@ const CustomMultiModal = ({ | ||||
|   secondCol, | ||||
|   length, | ||||
|   modalSize, | ||||
|   itemName, | ||||
|   noTable, | ||||
|   toggleAdd, | ||||
|   reset, | ||||
| }) => { | ||||
|   const [show, toggle] = useToggle(); | ||||
|  | ||||
|   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 `${length} ${t('common.items')}`; | ||||
|     return `Manage ${itemName} (${length})`; | ||||
|   }; | ||||
|  | ||||
|   const remove = (v) => { | ||||
| @@ -55,6 +57,10 @@ const CustomMultiModal = ({ | ||||
|     toggle(); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (show && reset !== null) reset(); | ||||
|   }, [show]); | ||||
|  | ||||
|   return ( | ||||
|     <CFormGroup row className="py-1"> | ||||
|       <CLabel col sm={firstCol} htmlFor="name"> | ||||
| @@ -69,6 +75,17 @@ const CustomMultiModal = ({ | ||||
|         <CModalHeader className="p-1"> | ||||
|           <CModalTitle className="pl-1 pt-1">{label}</CModalTitle> | ||||
|           <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')}> | ||||
|               <CButton color="primary" variant="outline" className="ml-2" onClick={closeAndSave}> | ||||
|                 <CIcon content={cilSave} /> | ||||
| @@ -83,6 +100,7 @@ const CustomMultiModal = ({ | ||||
|         </CModalHeader> | ||||
|         <CModalBody> | ||||
|           {children} | ||||
|           {!noTable ? ( | ||||
|             <CDataTable | ||||
|               addTableClasses="ignore-overflow" | ||||
|               items={value ?? []} | ||||
| @@ -92,13 +110,19 @@ const CustomMultiModal = ({ | ||||
|               scopedSlots={{ | ||||
|                 remove: (item) => ( | ||||
|                   <td className="align-middle text-center"> | ||||
|                   <CButton color="primary" variant="outline" size="sm" onClick={() => remove(item)}> | ||||
|                     <CButton | ||||
|                       color="primary" | ||||
|                       variant="outline" | ||||
|                       size="sm" | ||||
|                       onClick={() => remove(item)} | ||||
|                     > | ||||
|                       <CIcon content={cilMinus} /> | ||||
|                     </CButton> | ||||
|                   </td> | ||||
|                 ), | ||||
|               }} | ||||
|             /> | ||||
|           ) : null} | ||||
|         </CModalBody> | ||||
|       </CModal> | ||||
|     </CFormGroup> | ||||
| @@ -117,12 +141,19 @@ CustomMultiModal.propTypes = { | ||||
|   secondCol: PropTypes.string, | ||||
|   length: PropTypes.number.isRequired, | ||||
|   modalSize: PropTypes.string, | ||||
|   itemName: PropTypes.string.isRequired, | ||||
|   noTable: PropTypes.bool, | ||||
|   toggleAdd: PropTypes.func, | ||||
|   reset: PropTypes.func, | ||||
| }; | ||||
|  | ||||
| CustomMultiModal.defaultProps = { | ||||
|   firstCol: 6, | ||||
|   secondCol: 6, | ||||
|   modalSize: 'lg', | ||||
|   noTable: false, | ||||
|   toggleAdd: null, | ||||
|   reset: null, | ||||
| }; | ||||
|  | ||||
| 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; | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|       for (const opt of options) { | ||||
|         if (field.value === opt.value) return opt; | ||||
| @@ -34,7 +44,10 @@ const ConfigurationSelectField = ({ | ||||
|     } | ||||
|  | ||||
|     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; | ||||
| @@ -42,10 +55,22 @@ const ConfigurationSelectField = ({ | ||||
|  | ||||
|   const parseOptions = () => { | ||||
|     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; | ||||
|     } | ||||
|  | ||||
|     if (typeof field.options[0] === 'number') { | ||||
|       return field.options.map((opt) => ({ | ||||
|         value: opt, | ||||
|         label: `${opt}`, | ||||
|       })); | ||||
|     } | ||||
|  | ||||
|     if (field.options[0].includes('(')) { | ||||
|       return field.options.map((opt) => ({ | ||||
|         value: opt.split('(')[1].split(')')[0], | ||||
| @@ -54,7 +79,7 @@ const ConfigurationSelectField = ({ | ||||
|     } | ||||
|     return field.options.map((opt) => ({ | ||||
|       value: opt, | ||||
|       label: `opt${field.unit ?? ''}`, | ||||
|       label: `${opt}${field.unit ?? ''}`, | ||||
|     })); | ||||
|   }; | ||||
|  | ||||
| @@ -64,7 +89,7 @@ const ConfigurationSelectField = ({ | ||||
|         {label} | ||||
|       </CLabel> | ||||
|       <CCol sm={secondCol}> | ||||
|         <div style={{ width: width ?? '' }}> | ||||
|         <div style={{ maxWidth: width ?? '' }}> | ||||
|           <Select | ||||
|             name="Subsystems" | ||||
|             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 { CFormGroup, CCol, CLabel, CInput, CInvalidFeedback } from '@coreui/react'; | ||||
| import _ from 'lodash'; | ||||
| @@ -14,6 +14,7 @@ const StringField = ({ | ||||
|   disabled, | ||||
|   width, | ||||
|   placeholder, | ||||
|   extraButton, | ||||
| }) => { | ||||
|   const [localValue, setLocalValue] = useState(field.value); | ||||
|  | ||||
| @@ -41,6 +42,36 @@ const StringField = ({ | ||||
|     [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 ( | ||||
|     <CFormGroup row className="py-1"> | ||||
|       <CLabel col sm={firstCol} htmlFor="name"> | ||||
| @@ -56,7 +87,6 @@ const StringField = ({ | ||||
|             onChange={handleTyping} | ||||
|             invalid={field.error} | ||||
|             disabled={disabled} | ||||
|             maxLength="50" | ||||
|             placeholder={placeholder} | ||||
|           /> | ||||
|         </div> | ||||
| @@ -77,6 +107,7 @@ StringField.propTypes = { | ||||
|   disabled: PropTypes.bool.isRequired, | ||||
|   width: PropTypes.string, | ||||
|   placeholder: PropTypes.string, | ||||
|   extraButton: PropTypes.node, | ||||
| }; | ||||
|  | ||||
| StringField.defaultProps = { | ||||
| @@ -85,6 +116,7 @@ StringField.defaultProps = { | ||||
|   errorMessage: '', | ||||
|   width: null, | ||||
|   placeholder: null, | ||||
|   extraButton: null, | ||||
| }; | ||||
|  | ||||
| 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> | ||||
|         <CCol sm="4"> | ||||
|           <CSelect custom id="userRole" defaultValue="Admin" onChange={updateField}> | ||||
|             <option value="accounting">Accounting</option> | ||||
|             <option value="admin">Admin</option> | ||||
|             <option value="csr">CSR</option> | ||||
|             <option value="installer">Installer</option> | ||||
|             <option value="noc">NOC</option> | ||||
|             <option value="root">Root</option> | ||||
|             <option value="special">Special</option> | ||||
|             <option value="sub">Sub</option> | ||||
|   | ||||
| @@ -85,10 +85,10 @@ const EditConfigurationForm = ({ | ||||
|       <CFormGroup row> | ||||
|         <CCol> | ||||
|           <CRow className="pb-0"> | ||||
|             <CLabel md="3" col htmlFor="name"> | ||||
|             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|               <div>{t('user.name')}:</div> | ||||
|             </CLabel> | ||||
|             <CCol md="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               {editing ? ( | ||||
|                 <div> | ||||
|                   <CInput | ||||
| @@ -111,10 +111,10 @@ const EditConfigurationForm = ({ | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="pb-0"> | ||||
|             <CLabel md="3" col htmlFor="name"> | ||||
|             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|               <div>{t('user.description')}:</div> | ||||
|             </CLabel> | ||||
|             <CCol md="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               {editing ? ( | ||||
|                 <div> | ||||
|                   <CInput | ||||
| @@ -135,10 +135,10 @@ const EditConfigurationForm = ({ | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="pt-1"> | ||||
|             <CLabel md="3" col htmlFor="name"> | ||||
|             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|               <div>{t('configuration.device_types')}:</div> | ||||
|             </CLabel> | ||||
|             <CCol md="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               <Select | ||||
|                 isMulti | ||||
|                 closeMenuOnSelect={false} | ||||
| @@ -156,10 +156,10 @@ const EditConfigurationForm = ({ | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="pt-1 pb-0"> | ||||
|             <CLabel md="3" col htmlFor="name"> | ||||
|             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|               <div>RRM:</div> | ||||
|             </CLabel> | ||||
|             <CCol md="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               <div style={{ width: '120px' }}> | ||||
|                 <Select | ||||
|                   id="rrm" | ||||
| @@ -179,10 +179,10 @@ const EditConfigurationForm = ({ | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="py-1"> | ||||
|             <CLabel col md="4" xxl="3" htmlFor="firmwareUpgrade"> | ||||
|             <CLabel col lg="5" xl="3" htmlFor="firmwareUpgrade"> | ||||
|               Firmware Upgrade | ||||
|             </CLabel> | ||||
|             <CCol md="8" xxl="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               <div style={{ width: '120px' }}> | ||||
|                 <Select | ||||
|                   id="rrm" | ||||
| @@ -202,10 +202,10 @@ const EditConfigurationForm = ({ | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="py-1"> | ||||
|             <CLabel col md="3" htmlFor="firmwareRCOnly"> | ||||
|             <CLabel col lg="5" xl="3" htmlFor="firmwareRCOnly"> | ||||
|               Only Release Candidates | ||||
|             </CLabel> | ||||
|             <CCol xxl="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               <CSwitch | ||||
|                 id="firmwareRCOnly" | ||||
|                 color="primary" | ||||
| @@ -219,10 +219,10 @@ const EditConfigurationForm = ({ | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="pb-0"> | ||||
|             <CLabel md="3" col htmlFor="name"> | ||||
|             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|               <div>{t('configuration.used_by')}:</div> | ||||
|             </CLabel> | ||||
|             <CCol md="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               <CButton | ||||
|                 disabled={fields.inUse.value.length === 0} | ||||
|                 className="ml-0 pl-0" | ||||
| @@ -236,20 +236,20 @@ const EditConfigurationForm = ({ | ||||
|         </CCol> | ||||
|         <CCol className="mt-2"> | ||||
|           <CRow className="pb-0"> | ||||
|             <CLabel md="3" col htmlFor="name"> | ||||
|             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|               <div>{t('common.created')}:</div> | ||||
|             </CLabel> | ||||
|             <CCol md="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               <p className="mt-2 mb-0"> | ||||
|                 <FormattedDate date={fields.created.value} /> | ||||
|               </p> | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|           <CRow className="pb-0"> | ||||
|             <CLabel md="3" col htmlFor="name"> | ||||
|             <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|               <div>{t('common.modified')}:</div> | ||||
|             </CLabel> | ||||
|             <CCol md="9"> | ||||
|             <CCol lg="7" xl="9"> | ||||
|               <p className="mt-2 mb-0"> | ||||
|                 <FormattedDate date={fields.modified.value} /> | ||||
|               </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> | ||||
|       <CCol> | ||||
|         <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> | ||||
|           </CLabel> | ||||
|           <CCol md="8" xxl="9"> | ||||
|           <CCol lg="7" xxl="9"> | ||||
|             {editing ? ( | ||||
|               <div> | ||||
|                 <CInput | ||||
| @@ -57,10 +57,10 @@ const EditEntityForm = ({ | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|         <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> | ||||
|           </CLabel> | ||||
|           <CCol md="8" xxl="9"> | ||||
|           <CCol lg="7" xxl="9"> | ||||
|             {editing ? ( | ||||
|               <div> | ||||
|                 <CInput | ||||
| @@ -81,10 +81,10 @@ const EditEntityForm = ({ | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|         <CRow className="pb-0"> | ||||
|           <CLabel md="4" xxl="3" col htmlFor="name"> | ||||
|           <CLabel lg="5" xl="3" col htmlFor="name"> | ||||
|             <div>RRM:</div> | ||||
|           </CLabel> | ||||
|           <CCol md="8" xxl="9"> | ||||
|           <CCol lg="7" xxl="9"> | ||||
|             <div style={{ width: '120px' }}> | ||||
|               <Select | ||||
|                 id="rrm" | ||||
| @@ -101,10 +101,10 @@ const EditEntityForm = ({ | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|         <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> | ||||
|           </CLabel> | ||||
|           <CCol md="8" xxl="9"> | ||||
|           <CCol lg="7" xxl="9"> | ||||
|             {editing ? ( | ||||
|               <CButton className="pl-0 text-left" color="link" onClick={toggleAssociate}> | ||||
|                 {fields.deviceConfiguration.value === '' | ||||
| @@ -129,10 +129,10 @@ const EditEntityForm = ({ | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|         <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> | ||||
|           </CLabel> | ||||
|           <CCol md="8" xxl="9"> | ||||
|           <CCol lg="7" xxl="9"> | ||||
|             {editing ? ( | ||||
|               <CButton className="pl-0 text-left" color="link" onClick={toggleIpModal}> | ||||
|                 {fields.sourceIP.value.length === 0 | ||||
| @@ -151,10 +151,10 @@ const EditEntityForm = ({ | ||||
|           </CCol> | ||||
|         </CRow> | ||||
|         <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> | ||||
|           </CLabel> | ||||
|           <CCol md="8" xxl="9"> | ||||
|           <CCol lg="7" xxl="9"> | ||||
|             <div className="mt-2 mb-0"> | ||||
|               <FormattedDate date={fields.created.value} /> | ||||
|             </div> | ||||
| @@ -163,10 +163,10 @@ const EditEntityForm = ({ | ||||
|       </CCol> | ||||
|       <CCol className="mt-1"> | ||||
|         <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> | ||||
|           </CLabel> | ||||
|           <CCol md="8" xxl="9"> | ||||
|           <CCol lg="7" xxl="9"> | ||||
|             <div className="mt-2 mb-0"> | ||||
|               <FormattedDate date={fields.modified.value} /> | ||||
|             </div> | ||||
|   | ||||
| @@ -16,15 +16,19 @@ import { | ||||
|   CInputFile, | ||||
| } from '@coreui/react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import parsePhoneNumber from 'libphonenumber-js'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import ValidatePhoneNumberModal from 'components/ValidatePhoneNumberModal'; | ||||
| import NotesTable from '../NotesTable'; | ||||
| import LoadingButton from '../LoadingButton'; | ||||
| import Avatar from '../Avatar'; | ||||
| import useToggle from '../../hooks/useToggle'; | ||||
|  | ||||
| const EditMyProfile = ({ | ||||
|   t, | ||||
|   user, | ||||
|   updateUserWithId, | ||||
|   updateWithKey, | ||||
|   loading, | ||||
|   policies, | ||||
|   addNote, | ||||
| @@ -33,26 +37,49 @@ const EditMyProfile = ({ | ||||
|   deleteAvatar, | ||||
|   showPreview, | ||||
|   fileInputKey, | ||||
|   sendPhoneNumberTest, | ||||
|   testVerificationCode, | ||||
| }) => { | ||||
|   const [showPhoneModal, togglePhoneModal] = useToggle(false); | ||||
|   const [showPassword, setShowPassword] = useState(false); | ||||
|  | ||||
|   const toggleShowPassword = () => { | ||||
|     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 ( | ||||
|     <CForm> | ||||
|       <CFormGroup row> | ||||
|         <CLabel sm="2" col htmlFor="name"> | ||||
|         <CLabel lg="2" xxl="1" col htmlFor="name"> | ||||
|           {t('user.name')} | ||||
|         </CLabel> | ||||
|         <CCol sm="4"> | ||||
|         <CCol lg="4" xxl="5"> | ||||
|           <CInput id="name" value={user.name.value} onChange={updateUserWithId} maxLength="20" /> | ||||
|         </CCol> | ||||
|         <CLabel sm="2" col htmlFor="description"> | ||||
|         <CLabel lg="2" xxl="1" col htmlFor="description"> | ||||
|           {t('user.description')} | ||||
|         </CLabel> | ||||
|         <CCol sm="4"> | ||||
|         <CCol lg="4" xxl="5"> | ||||
|           <CInput | ||||
|             id="description" | ||||
|             value={user.description.value} | ||||
| @@ -62,11 +89,17 @@ const EditMyProfile = ({ | ||||
|         </CCol> | ||||
|       </CFormGroup> | ||||
|       <CFormGroup row> | ||||
|         <CLabel sm="2" col htmlFor="userRole"> | ||||
|         <CLabel lg="2" xxl="1" col htmlFor="userRole"> | ||||
|           {t('user.user_role')} | ||||
|         </CLabel> | ||||
|         <CCol sm="4"> | ||||
|           <CSelect custom id="userRole" onChange={updateUserWithId} value={user.userRole.value}> | ||||
|         <CCol lg="4" xxl="5"> | ||||
|           <CSelect | ||||
|             custom | ||||
|             id="userRole" | ||||
|             onChange={updateUserWithId} | ||||
|             value={user.userRole.value} | ||||
|             style={{ width: '100px' }} | ||||
|           > | ||||
|             <option value="admin">Admin</option> | ||||
|             <option value="csr">CSR</option> | ||||
|             <option value="root">Root</option> | ||||
| @@ -75,10 +108,10 @@ const EditMyProfile = ({ | ||||
|             <option value="system">System</option> | ||||
|           </CSelect> | ||||
|         </CCol> | ||||
|         <CLabel sm="2" col htmlFor="currentPassword"> | ||||
|         <CLabel lg="2" xxl="1" col htmlFor="currentPassword"> | ||||
|           {t('login.new_password')} | ||||
|         </CLabel> | ||||
|         <CCol sm="4"> | ||||
|         <CCol lg="4" xxl="5"> | ||||
|           <CInputGroup> | ||||
|             <CInput | ||||
|               type={showPassword ? 'text' : 'password'} | ||||
| @@ -103,25 +136,42 @@ const EditMyProfile = ({ | ||||
|         </CCol> | ||||
|       </CFormGroup> | ||||
|       <CFormGroup row> | ||||
|         <CCol sm="6"> | ||||
|           <NotesTable | ||||
|             t={t} | ||||
|             notes={user.notes.value} | ||||
|             addNote={addNote} | ||||
|             loading={loading} | ||||
|             size="lg" | ||||
|           /> | ||||
|         <CLabel lg="2" xxl="1" col htmlFor="userRole"> | ||||
|           {t('user.user_role')} | ||||
|         </CLabel> | ||||
|         <CCol lg="4" xxl="5"> | ||||
|           <CSelect | ||||
|             custom | ||||
|             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> | ||||
|         <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')} | ||||
|         </CLabel> | ||||
|         <CCol sm="4"> | ||||
|         <CCol lg="10" xl="5" className="pt-2"> | ||||
|           <CRow> | ||||
|             <CCol sm="3" className="pt-2"> | ||||
|             <CCol lg="2" xl="2" className="pt-2"> | ||||
|               {t('common.current')} | ||||
|               <div className="pt-5">Preview</div> | ||||
|             </CCol> | ||||
|             <CCol sm="2"> | ||||
|             <CCol lg="1" xl="1"> | ||||
|               <Avatar src={avatar} fallback={user.email.value} /> | ||||
|               <div className="pt-3"> | ||||
|                 <Avatar src={newAvatar} fallback={user.email.value} /> | ||||
| @@ -150,6 +200,15 @@ const EditMyProfile = ({ | ||||
|             </CCol> | ||||
|           </CRow> | ||||
|         </CCol> | ||||
|         <CCol lg="12" xl="6" className="pt-2"> | ||||
|           <NotesTable | ||||
|             t={t} | ||||
|             notes={user.notes.value} | ||||
|             addNote={addNote} | ||||
|             loading={loading} | ||||
|             size="lg" | ||||
|           /> | ||||
|         </CCol> | ||||
|       </CFormGroup> | ||||
|       <CRow> | ||||
|         <CCol /> | ||||
| @@ -165,6 +224,14 @@ const EditMyProfile = ({ | ||||
|           </CLink> | ||||
|         </CCol> | ||||
|       </CRow> | ||||
|       <ValidatePhoneNumberModal | ||||
|         t={t} | ||||
|         show={showPhoneModal} | ||||
|         toggle={togglePhoneModal} | ||||
|         sendPhoneNumberTest={sendPhoneNumberTest} | ||||
|         save={saveNumber} | ||||
|         testVerificationCode={testVerificationCode} | ||||
|       /> | ||||
|     </CForm> | ||||
|   ); | ||||
| }; | ||||
| @@ -181,6 +248,9 @@ EditMyProfile.propTypes = { | ||||
|   showPreview: PropTypes.func.isRequired, | ||||
|   deleteAvatar: PropTypes.func.isRequired, | ||||
|   fileInputKey: PropTypes.number.isRequired, | ||||
|   sendPhoneNumberTest: PropTypes.func.isRequired, | ||||
|   testVerificationCode: PropTypes.func.isRequired, | ||||
|   updateWithKey: PropTypes.func.isRequired, | ||||
| }; | ||||
|  | ||||
| EditMyProfile.defaultProps = { | ||||
|   | ||||
| @@ -53,8 +53,11 @@ const EditUserForm = ({ t, user, updateUserWithId, loading, policies, addNote }) | ||||
|         </CLabel> | ||||
|         <CCol sm="4"> | ||||
|           <CSelect custom id="userRole" onChange={updateUserWithId} value={user.userRole.value}> | ||||
|             <option value="accounting">Accounting</option> | ||||
|             <option value="admin">Admin</option> | ||||
|             <option value="csr">CSR</option> | ||||
|             <option value="installer">Installer</option> | ||||
|             <option value="noc">NOC</option> | ||||
|             <option value="root">Root</option> | ||||
|             <option value="special">Special</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 ReactPaginate from 'react-paginate'; | ||||
| 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 ReactTooltip from 'react-tooltip'; | ||||
| import DeleteButton from './DeleteButton'; | ||||
| @@ -28,7 +28,9 @@ const InventoryTable = ({ | ||||
|   toggleAssociate, | ||||
|   toggleAssocEntity, | ||||
|   toggleComputed, | ||||
|   pushConfig, | ||||
| }) => { | ||||
|   const [gwUi] = useState(localStorage.getItem('owgw-ui')); | ||||
|   const columns = | ||||
|     onlyEntity || onlyUnassigned | ||||
|       ? [ | ||||
| @@ -75,7 +77,19 @@ const InventoryTable = ({ | ||||
|         border | ||||
|         loading={loading} | ||||
|         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>, | ||||
|           created: (item) => ( | ||||
|             <td className="align-middle"> | ||||
| @@ -97,9 +111,9 @@ const InventoryTable = ({ | ||||
|                   }) | ||||
|                 } | ||||
|               > | ||||
|                 {item.deviceConfigurationName === '' | ||||
|                 {item.deviceConfiguration === '' | ||||
|                   ? t('configuration.add_or_link') | ||||
|                   : item.deviceConfigurationName} | ||||
|                   : item.extendedInfo?.deviceConfiguration?.name ?? item.deviceConfiguration} | ||||
|               </CButton> | ||||
|             </td> | ||||
|           ), | ||||
| @@ -113,7 +127,7 @@ const InventoryTable = ({ | ||||
|                   aria-current="page" | ||||
|                   to={() => `/entity/${item.entity}`} | ||||
|                 > | ||||
|                   {item.entity_info ? item.entity_info.name : item.entity} | ||||
|                   {item.extendedInfo?.entity?.name ?? item.entity} | ||||
|                 </CLink> | ||||
|               )} | ||||
|             </td> | ||||
| @@ -128,7 +142,7 @@ const InventoryTable = ({ | ||||
|                   aria-current="page" | ||||
|                   to={() => `/venue/${item.venue}`} | ||||
|                 > | ||||
|                   {item.venue_info ? item.venue_info.name : item.venue} | ||||
|                   {item.extendedInfo?.venue?.name ?? item.venue} | ||||
|                 </CLink> | ||||
|               )} | ||||
|             </td> | ||||
| @@ -138,7 +152,7 @@ const InventoryTable = ({ | ||||
|               <CButtonToolbar | ||||
|                 role="group" | ||||
|                 className="justify-content-flex-end" | ||||
|                 style={{ width: '250px' }} | ||||
|                 style={{ width: '300px' }} | ||||
|               > | ||||
|                 <CPopover content={t('inventory.assign_ent_ven')}> | ||||
|                   <div> | ||||
| @@ -167,6 +181,20 @@ const InventoryTable = ({ | ||||
|                   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"> | ||||
|                   <CButton | ||||
|                     color="primary" | ||||
| @@ -255,6 +283,7 @@ InventoryTable.propTypes = { | ||||
|   toggleAssociate: PropTypes.func.isRequired, | ||||
|   toggleAssocEntity: PropTypes.func.isRequired, | ||||
|   toggleComputed: PropTypes.func.isRequired, | ||||
|   pushConfig: PropTypes.func.isRequired, | ||||
| }; | ||||
|  | ||||
| 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,14 +106,13 @@ const LoginForm = ({ | ||||
|         </CAlert> | ||||
|       </CCol> | ||||
|     </CRow> | ||||
|     <CRow> | ||||
|       <CCol> | ||||
|     <div className="d-flex flex-row align-middle"> | ||||
|       <CButton color="primary" className="px-4" onClick={signIn} disabled={loading}> | ||||
|         {loading ? t('login.logging_in') : t('login.login')} | ||||
|         <CSpinner hidden={!loading} color="light" component="span" size="sm" /> | ||||
|       </CButton> | ||||
|       <CLink | ||||
|           className="c-subheader-nav-link px-3" | ||||
|         className="c-subheader-nav-link px-3 align-self-center" | ||||
|         aria-current="page" | ||||
|         href={policies.accessPolicy} | ||||
|         target="_blank" | ||||
| @@ -122,7 +121,7 @@ const LoginForm = ({ | ||||
|         {t('common.access_policy')} | ||||
|       </CLink> | ||||
|       <CLink | ||||
|           className="c-subheader-nav-link" | ||||
|         className="c-subheader-nav-link align-self-center" | ||||
|         aria-current="page" | ||||
|         href={policies.passwordPolicy} | ||||
|         target="_blank" | ||||
| @@ -130,13 +129,10 @@ const LoginForm = ({ | ||||
|       > | ||||
|         {t('common.password_policy')} | ||||
|       </CLink> | ||||
|       </CCol> | ||||
|       <CCol xs="5" className="text-right"> | ||||
|         <CButton variant="ghost" color="primary" onClick={toggleForgotPassword}> | ||||
|       <CButton className="ml-auto" variant="ghost" color="primary" onClick={toggleForgotPassword}> | ||||
|         {t('common.forgot_password')} | ||||
|       </CButton> | ||||
|       </CCol> | ||||
|     </CRow> | ||||
|     </div> | ||||
|   </CForm> | ||||
| ); | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import styles from './index.module.scss'; | ||||
| import LoginForm from './LoginForm'; | ||||
| import ForgotPasswordForm from './ForgotPasswordForm'; | ||||
| import ChangePasswordForm from './ChangePasswordForm'; | ||||
| import AccountVerificationForm from './AccountVerificationForm'; | ||||
|  | ||||
| const LoginPage = ({ | ||||
|   t, | ||||
| @@ -16,17 +17,32 @@ const LoginPage = ({ | ||||
|   forgotResponse, | ||||
|   fields, | ||||
|   updateField, | ||||
|   isLogin, | ||||
|   isPasswordChange, | ||||
|   formType, | ||||
|   toggleForgotPassword, | ||||
|   onKeyDown, | ||||
|   sendForgotPasswordEmail, | ||||
|   changePasswordResponse, | ||||
|   cancelPasswordChange, | ||||
|   policies, | ||||
|   validateCode, | ||||
|   resendValidationCode, | ||||
| }) => { | ||||
|   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 ( | ||||
|         <ForgotPasswordForm | ||||
|           t={t} | ||||
| @@ -43,7 +59,7 @@ const LoginPage = ({ | ||||
|         /> | ||||
|       ); | ||||
|     } | ||||
|     if (isPasswordChange) { | ||||
|     if (formType === 'change-password') { | ||||
|       return ( | ||||
|         <ChangePasswordForm | ||||
|           t={t} | ||||
| @@ -86,7 +102,7 @@ const LoginPage = ({ | ||||
|               alt="OpenWifi" | ||||
|             /> | ||||
|             <CCardGroup> | ||||
|               <CCard className="p-4" color={isLogin && isPasswordChange ? 'secondary' : ''}> | ||||
|               <CCard className="p-4" color={formType === 'change-password' ? 'secondary' : ''}> | ||||
|                 <CCardBody>{getForm()}</CCardBody> | ||||
|               </CCard> | ||||
|             </CCardGroup> | ||||
| @@ -107,14 +123,15 @@ LoginPage.propTypes = { | ||||
|   forgotResponse: PropTypes.instanceOf(Object).isRequired, | ||||
|   fields: PropTypes.instanceOf(Object).isRequired, | ||||
|   updateField: PropTypes.func.isRequired, | ||||
|   isLogin: PropTypes.bool.isRequired, | ||||
|   isPasswordChange: PropTypes.bool.isRequired, | ||||
|   toggleForgotPassword: PropTypes.func.isRequired, | ||||
|   onKeyDown: PropTypes.func.isRequired, | ||||
|   sendForgotPasswordEmail: PropTypes.func.isRequired, | ||||
|   changePasswordResponse: PropTypes.instanceOf(Object).isRequired, | ||||
|   cancelPasswordChange: PropTypes.func.isRequired, | ||||
|   policies: PropTypes.instanceOf(Object).isRequired, | ||||
|   formType: PropTypes.string.isRequired, | ||||
|   validateCode: PropTypes.func.isRequired, | ||||
|   resendValidationCode: PropTypes.func.isRequired, | ||||
| }; | ||||
|  | ||||
| export default React.memo(LoginPage); | ||||
|   | ||||
| @@ -12,9 +12,10 @@ import { | ||||
| } from '@coreui/react'; | ||||
| import { cilBan, cilCheckCircle, cilPencil, cilPlus, cilSync, cilTrash } from '@coreui/icons'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { capitalizeFirstLetter, prettyDate } from '../../utils/formatting'; | ||||
| import { capitalizeFirstLetter } from '../../utils/formatting'; | ||||
| import DeleteModal from '../DeleteModal'; | ||||
| import Avatar from '../Avatar'; | ||||
| import FormattedDate from '../FormattedDate'; | ||||
|  | ||||
| const UserListTable = ({ | ||||
|   t, | ||||
| @@ -115,7 +116,9 @@ const UserListTable = ({ | ||||
|                 </td> | ||||
|               ), | ||||
|               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) => ( | ||||
|                 <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, | ||||
|   CPopover, | ||||
| } from '@coreui/react'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import { cilPencil, cilPlus, cilSync } from '@coreui/icons'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import DeleteButton from './DeleteButton'; | ||||
| @@ -33,6 +34,8 @@ const VenueTable = ({ | ||||
|   deleteVenue, | ||||
|   refresh, | ||||
| }) => { | ||||
|   const history = useHistory(); | ||||
|  | ||||
|   const columns = [ | ||||
|     { key: 'name', label: t('user.name'), _style: { width: '20%' } }, | ||||
|     { key: 'description', label: t('user.description'), _style: { width: '25%' } }, | ||||
| @@ -121,13 +124,12 @@ const VenueTable = ({ | ||||
|                   > | ||||
|                     <CPopover content="Edit Tag"> | ||||
|                       <CButton | ||||
|                         disabled | ||||
|                         color="primary" | ||||
|                         variant="outline" | ||||
|                         shape="square" | ||||
|                         size="sm" | ||||
|                         className="mx-1" | ||||
|                         onClick={() => {}} | ||||
|                         onClick={() => history.push(`/venue/${item.id}`)} | ||||
|                         style={{ width: '33px', height: '30px' }} | ||||
|                       > | ||||
|                         <CIcon name="cil-pencil" content={cilPencil} size="sm" /> | ||||
|   | ||||
| @@ -22,6 +22,8 @@ export const AuthProvider = ({ axiosInstance, token, apiEndpoints, children }) = | ||||
|       .then(() => {}) | ||||
|       .catch(() => {}) | ||||
|       .finally(() => { | ||||
|         localStorage.removeItem('access_token'); | ||||
|         localStorage.removeItem('gateway_endpoints'); | ||||
|         sessionStorage.clear(); | ||||
|         window.location.replace('/'); | ||||
|       }); | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { useHistory } from 'react-router-dom'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { v4 as createUuid } from 'uuid'; | ||||
| import { set as lodashSet, get as lodashGet } from 'lodash'; | ||||
| import { useAuth } from '../AuthProvider'; | ||||
|  | ||||
| @@ -14,6 +15,7 @@ const navbarOption = ({ | ||||
|   children, | ||||
|   childrenEntities, | ||||
|   childrenVenues, | ||||
|   extraData = {}, | ||||
| }) => { | ||||
|   let tag = 'SidebarChildless'; | ||||
|   if (children) tag = 'SidebarDropdown'; | ||||
| @@ -25,7 +27,8 @@ const navbarOption = ({ | ||||
|     name, | ||||
|     path, | ||||
|     isVenue, | ||||
|     onClick: () => selectEntity(uuid, name, path, isVenue, childrenEntities, childrenVenues), | ||||
|     onClick: () => | ||||
|       selectEntity(uuid, name, path, isVenue, childrenEntities, childrenVenues, extraData), | ||||
|     _children: children, | ||||
|   }; | ||||
| }; | ||||
| @@ -42,7 +45,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | ||||
|   const [parentsWithChildrenLoaded, setParentsWithChildrenLoaded] = 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 (childrenEntities || childrenVenues) { | ||||
|       setEntityToRetrieve({ childrenEntities, childrenVenues, path, uuid, isVenue }); | ||||
| @@ -54,6 +57,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | ||||
|       childrenEntities, | ||||
|       childrenVenues, | ||||
|       path, | ||||
|       extraData, | ||||
|     }); | ||||
|     history.push(`/${isVenue ? 'venue' : 'entity'}/${uuid}`); | ||||
|   }; | ||||
| @@ -98,7 +102,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | ||||
|   const setProviderEntity = async (id, isVenue) => { | ||||
|     const newEntity = await getEntity(id, isVenue); | ||||
|     if (newEntity) { | ||||
|       setEntity({ | ||||
|       const newObj = { | ||||
|         ...newEntity, | ||||
|         uuid: newEntity.id, | ||||
|         name: newEntity.name, | ||||
| @@ -106,7 +110,11 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | ||||
|         isVenue, | ||||
|         childrenIds: newEntity.children, | ||||
|         childrenVenues: newEntity.venues, | ||||
|       }); | ||||
|         extraData: newEntity, | ||||
|         refreshId: createUuid(), | ||||
|       }; | ||||
|  | ||||
|       setEntity({ ...newObj }); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
| @@ -162,6 +170,7 @@ export const EntityProvider = ({ axiosInstance, selectedEntity, children }) => { | ||||
|           children: nestedOptions, | ||||
|           childrenEntities: grandChildrenEntities, | ||||
|           childrenVenues: grandChildrenVenues, | ||||
|           extraData: result, | ||||
|         }); | ||||
|       }); | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,11 @@ const hasError = (e, field) => { | ||||
|       return `This field has a minimum value of ${field.minimum}`; | ||||
|     if (field.maximum !== undefined && e.target.value > 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; | ||||
| }; | ||||
| @@ -44,5 +49,15 @@ export default (initialState) => { | ||||
|         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 | ||||
| export { default as AddConfigurationForm } from './components/AddConfigurationForm'; | ||||
| export { default as AddContactForm } from './components/AddContactForm'; | ||||
| export { default as AddEntityForm } from './components/AddEntityForm'; | ||||
| export { default as AddInventoryTagForm } from './components/AddInventoryTagForm'; | ||||
| 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 ConfigurationCustomMultiModal } from './components/Configuration/CustomMultiModal'; | ||||
| 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 ConfigurationIntField } from './components/Configuration/IntField'; | ||||
| 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 ConfigurationToggle } from './components/Configuration/Toggle'; | ||||
| export { default as ConfirmFooter } from './components/ConfirmFooter'; | ||||
| export { default as ContactTable } from './components/ContactTable'; | ||||
| export { default as CopyToClipboardButton } from './components/CopyToClipboardButton'; | ||||
| export { default as CreateUserForm } from './components/CreateUserForm'; | ||||
| 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 DeviceSearchBar } from './components/DeviceSearchBar'; | ||||
| export { default as EditConfigurationForm } from './components/EditConfigurationForm'; | ||||
| export { default as EditContactForm } from './components/EditContactForm'; | ||||
| export { default as EditEntityForm } from './components/EditEntityForm'; | ||||
| export { default as EditInventoryTagForm } from './components/EditInventoryTagForm'; | ||||
| 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 EventQueueModal } from './components/EventQueueModal'; | ||||
| export { default as InventoryTable } from './components/InventoryTable'; | ||||
| export { default as FileToStringButton } from './components/FileToStringButton'; | ||||
| export { default as FirmwareHistoryTable } from './components/FirmwareHistoryTable'; | ||||
| export { default as FirmwareList } from './components/FirmwareList'; | ||||
| export { default as FormattedDate } from './components/FormattedDate'; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Charles
					Charles