mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
synced 2025-10-30 02:12:22 +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,22 +100,29 @@ const CustomMultiModal = ({
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
{children}
|
||||
<CDataTable
|
||||
addTableClasses="ignore-overflow"
|
||||
items={value ?? []}
|
||||
fields={columns}
|
||||
hover
|
||||
border
|
||||
scopedSlots={{
|
||||
remove: (item) => (
|
||||
<td className="align-middle text-center">
|
||||
<CButton color="primary" variant="outline" size="sm" onClick={() => remove(item)}>
|
||||
<CIcon content={cilMinus} />
|
||||
</CButton>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{!noTable ? (
|
||||
<CDataTable
|
||||
addTableClasses="ignore-overflow"
|
||||
items={value ?? []}
|
||||
fields={columns}
|
||||
hover
|
||||
border
|
||||
scopedSlots={{
|
||||
remove: (item) => (
|
||||
<td className="align-middle text-center">
|
||||
<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,37 +106,33 @@ const LoginForm = ({
|
||||
</CAlert>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<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"
|
||||
aria-current="page"
|
||||
href={policies.accessPolicy}
|
||||
target="_blank"
|
||||
hidden={policies.accessPolicy.length === 0}
|
||||
>
|
||||
{t('common.access_policy')}
|
||||
</CLink>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
href={policies.passwordPolicy}
|
||||
target="_blank"
|
||||
hidden={policies.passwordPolicy.length === 0}
|
||||
>
|
||||
{t('common.password_policy')}
|
||||
</CLink>
|
||||
</CCol>
|
||||
<CCol xs="5" className="text-right">
|
||||
<CButton variant="ghost" color="primary" onClick={toggleForgotPassword}>
|
||||
{t('common.forgot_password')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<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 align-self-center"
|
||||
aria-current="page"
|
||||
href={policies.accessPolicy}
|
||||
target="_blank"
|
||||
hidden={policies.accessPolicy.length === 0}
|
||||
>
|
||||
{t('common.access_policy')}
|
||||
</CLink>
|
||||
<CLink
|
||||
className="c-subheader-nav-link align-self-center"
|
||||
aria-current="page"
|
||||
href={policies.passwordPolicy}
|
||||
target="_blank"
|
||||
hidden={policies.passwordPolicy.length === 0}
|
||||
>
|
||||
{t('common.password_policy')}
|
||||
</CLink>
|
||||
<CButton className="ml-auto" variant="ghost" color="primary" onClick={toggleForgotPassword}>
|
||||
{t('common.forgot_password')}
|
||||
</CButton>
|
||||
</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