forked from ddrilling/asb_cloud_front
* Фабрики фильтров вынесены в файл utils/table
* Добавлена константа defaultPagination * UserController и WellController перемещены в одноимённые директории и разделены по компонентам * Ширина колонки прав ролей освобождена * Назначение типа компании изменено на Select по id
This commit is contained in:
parent
4e9a541cdb
commit
1b42f77fef
@ -12,6 +12,11 @@ export { SelectFromDictionary } from './SelectFromDictionary'
|
||||
|
||||
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
|
||||
|
||||
export const defaultPagination = {
|
||||
defaultPageSize: 14,
|
||||
showSizeChanger: true,
|
||||
}
|
||||
|
||||
export const makeNumericRender = (fixed?: number) => (value: any, row: object): ReactNode => {
|
||||
let val = '-'
|
||||
if ((value ?? null) !== null && Number.isFinite(+value)) {
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
makeColumn,
|
||||
makeSelectColumn,
|
||||
makeActionHandler,
|
||||
makeStringSorter
|
||||
makeStringSorter,
|
||||
defaultPagination
|
||||
} from '../../components/Table'
|
||||
import { AdminClusterService, AdminDepositService } from '../../services/api'
|
||||
import { arrayOrDefault } from '../../utils'
|
||||
@ -69,8 +70,9 @@ export const ClusterController = () => {
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
columns={clusterColumns}
|
||||
dataSource={clusters}
|
||||
columns={clusterColumns}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={makeActionHandler('insert', handlerProps)}
|
||||
onRowEdit={makeActionHandler('put', handlerProps)}
|
||||
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||
|
@ -2,39 +2,52 @@ import { useEffect, useState } from 'react'
|
||||
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { EditableTable, makeColumn, makeActionHandler, makeStringSorter } from '../../components/Table'
|
||||
import { AdminCompanyService } from '../../services/api'
|
||||
import {
|
||||
EditableTable,
|
||||
makeColumn,
|
||||
makeActionHandler,
|
||||
makeStringSorter,
|
||||
makeSelectColumn,
|
||||
defaultPagination
|
||||
} from '../../components/Table'
|
||||
import { AdminCompanyService, AdminCompanyTypeService } from '../../services/api'
|
||||
import { arrayOrDefault } from '../../utils'
|
||||
import { min1 } from '../../utils/validationRules'
|
||||
|
||||
const companyColumns = [
|
||||
makeColumn('Название', 'caption', {
|
||||
width: 200,
|
||||
editable: true,
|
||||
sorter: makeStringSorter('caption'),
|
||||
formItemRules: min1,
|
||||
}),
|
||||
makeColumn('Тип компании', 'companyTypeCaption', {
|
||||
width: 200,
|
||||
editable: true,
|
||||
sorter: makeStringSorter('companyTypeCaption')
|
||||
}),
|
||||
]
|
||||
|
||||
export default function CompanyController() {
|
||||
export const CompanyController = () => {
|
||||
const [columns, setColumns] = useState([])
|
||||
const [companies, setCompanies] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const updateTable = () => invokeWebApiWrapperAsync(
|
||||
async() => {
|
||||
const companies = await AdminCompanyService.getAll()
|
||||
setCompanies(arrayOrDefault(companies))
|
||||
},
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список кустов`
|
||||
)
|
||||
const updateTable = async () => {
|
||||
const companies = await AdminCompanyService.getAll()
|
||||
setCompanies(arrayOrDefault(companies))
|
||||
}
|
||||
|
||||
useEffect(updateTable, [])
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async() => {
|
||||
const companyTypes = arrayOrDefault(await AdminCompanyTypeService.getAll()).map((companyType) => ({
|
||||
value: companyType.id,
|
||||
label: companyType.caption,
|
||||
}))
|
||||
|
||||
setColumns([
|
||||
makeColumn('Название', 'caption', {
|
||||
width: 200,
|
||||
editable: true,
|
||||
sorter: makeStringSorter('caption'),
|
||||
formItemRules: min1,
|
||||
}),
|
||||
makeSelectColumn('Тип компании', 'idCompanyType', companyTypes, null, {
|
||||
width: 200,
|
||||
editable: true
|
||||
}),
|
||||
])
|
||||
|
||||
await updateTable()
|
||||
}
|
||||
), [])
|
||||
|
||||
const handlerProps = {
|
||||
service: AdminCompanyService,
|
||||
@ -48,8 +61,9 @@ export default function CompanyController() {
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
columns={companyColumns}
|
||||
columns={columns}
|
||||
dataSource={companies}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={makeActionHandler('insert', handlerProps)}
|
||||
onRowEdit={makeActionHandler('put', handlerProps)}
|
||||
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||
@ -57,3 +71,5 @@ export default function CompanyController() {
|
||||
</LoaderPortal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompanyController
|
||||
|
@ -2,7 +2,13 @@ import { useEffect, useState } from 'react'
|
||||
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { EditableTable, makeColumn, makeActionHandler, makeStringSorter } from '../../components/Table'
|
||||
import {
|
||||
EditableTable,
|
||||
makeColumn,
|
||||
makeActionHandler,
|
||||
makeStringSorter,
|
||||
defaultPagination
|
||||
} from '../../components/Table'
|
||||
import { AdminCompanyTypeService } from '../../services/api'
|
||||
import { arrayOrDefault } from '../../utils'
|
||||
import { min1 } from '../../utils/validationRules'
|
||||
@ -16,11 +22,6 @@ const columns = [
|
||||
}),
|
||||
]
|
||||
|
||||
const pagination = {
|
||||
defaultPageSize: 16,
|
||||
showSizeChanger: true,
|
||||
}
|
||||
|
||||
export const CompanyTypeController = () => {
|
||||
const [companyTypes, setCompanyTypes] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
@ -50,7 +51,7 @@ export const CompanyTypeController = () => {
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={companyTypes}
|
||||
pagination={pagination}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={makeActionHandler('insert', handlerProps)}
|
||||
onRowEdit={makeActionHandler('put', handlerProps)}
|
||||
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { EditableTable, makeColumn, makeActionHandler } from '../../components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import { EditableTable, makeColumn, makeActionHandler, defaultPagination } from '../../components/Table'
|
||||
import { AdminDepositService } from '../../services/api'
|
||||
import { arrayOrDefault } from '../../utils'
|
||||
import { min1 } from '../../utils/validationRules'
|
||||
@ -14,7 +15,7 @@ const depositColumns = [
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed })
|
||||
]
|
||||
|
||||
export default function DepositController() {
|
||||
export const DepositController = () => {
|
||||
const [deposits, setDeposits] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
@ -41,8 +42,9 @@ export default function DepositController() {
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
columns={depositColumns}
|
||||
dataSource={deposits}
|
||||
columns={depositColumns}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={makeActionHandler('insert', handlerProps)}
|
||||
onRowEdit={makeActionHandler('put', handlerProps)}
|
||||
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||
@ -50,3 +52,5 @@ export default function DepositController() {
|
||||
</LoaderPortal>
|
||||
)
|
||||
}
|
||||
|
||||
export default DepositController
|
||||
|
@ -16,7 +16,7 @@ export const RoleController = memo(() => {
|
||||
|
||||
const loadRoles = async () => {
|
||||
const roles = await AdminUserRoleService.getAll()
|
||||
setRoles(Array.isArray(roles) ? roles : [])
|
||||
setRoles(arrayOrDefault(roles))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -28,7 +28,6 @@ export const RoleController = memo(() => {
|
||||
editable: true
|
||||
}, { allowClear: true }),
|
||||
makeTagColumn('Разрешения', 'permissions', permissions, 'id', 'name', {
|
||||
width: 200,
|
||||
editable: true,
|
||||
render: (permission) => <PermissionView info={permission} />,
|
||||
}),
|
||||
|
27
src/pages/AdminPanel/UserController/RoleTag.jsx
Normal file
27
src/pages/AdminPanel/UserController/RoleTag.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { Select } from 'antd'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
|
||||
export const RoleTag = memo(({ roles, value, onChange }) => {
|
||||
const [options, setOptions] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(roles.map((elm) => ({
|
||||
key: Date.now(),
|
||||
value: `${elm.caption}`,
|
||||
label: elm.caption
|
||||
})))
|
||||
}, [roles])
|
||||
|
||||
return (
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
mode={'tags'}
|
||||
options={options}
|
||||
value={value ?? []}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export default RoleTag
|
@ -1,75 +1,29 @@
|
||||
import { Button, Select, Tag } from 'antd'
|
||||
import { Button, Tag } from 'antd'
|
||||
import { UserSwitchOutlined } from '@ant-design/icons'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { RoleView } from '../../components/Views'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { ChangePassword } from '../../components/ChangePassword'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import { RoleView } from '../../../components/Views'
|
||||
import LoaderPortal from '../../../components/LoaderPortal'
|
||||
import { ChangePassword } from '../../../components/ChangePassword'
|
||||
import { invokeWebApiWrapperAsync } from '../../../components/factory'
|
||||
import {
|
||||
EditableTable,
|
||||
makeColumn,
|
||||
makeSelectColumn,
|
||||
makeActionHandler,
|
||||
makeStringSorter,
|
||||
makeNumericSorter
|
||||
} from '../../components/Table'
|
||||
import { AdminCompanyService, AdminUserRoleService, AdminUserService } from '../../services/api'
|
||||
import { createLoginRules, nameRules, phoneRules, emailRules } from '../../utils/validationRules'
|
||||
import { arrayOrDefault } from '../../utils'
|
||||
makeNumericSorter,
|
||||
defaultPagination
|
||||
} from '../../../components/Table'
|
||||
import { AdminCompanyService, AdminUserRoleService, AdminUserService } from '../../../services/api'
|
||||
import { createLoginRules, nameRules, phoneRules, emailRules } from '../../../utils/validationRules'
|
||||
import { arrayOrDefault } from '../../../utils'
|
||||
|
||||
const RoleTag = memo(({ roles, value, onChange }) => {
|
||||
const [options, setOptions] = useState([])
|
||||
import RoleTag from './RoleTag'
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(roles.map((elm) => ({
|
||||
key: Date.now(),
|
||||
value: `${elm.caption}`,
|
||||
label: elm.caption
|
||||
})))
|
||||
}, [roles])
|
||||
import { makeTextOnFilter, makeTextFilters } from '../../../utils/table'
|
||||
|
||||
return (
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
mode={'tags'}
|
||||
options={options}
|
||||
value={value ?? []}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const makeOnFilter = (key) => (value, record) => record?.[key]?.startsWith(value)
|
||||
const makeDataFilters = (array, keys) => {
|
||||
const filters = Array(keys.length)
|
||||
|
||||
for (let i = 0; i < keys.length; i++)
|
||||
filters[i] = []
|
||||
|
||||
array.forEach((row) => {
|
||||
if (!row) return
|
||||
keys.forEach((key, idx) => {
|
||||
if (row[key] && filters[idx].indexOf(row[key]) < 0)
|
||||
filters[idx].push(row[key])
|
||||
})
|
||||
})
|
||||
|
||||
const out = {}
|
||||
|
||||
keys.forEach((key, idx) => {
|
||||
filters[idx].sort()
|
||||
out[key] = filters[idx].map((filter) => ({
|
||||
value: filter,
|
||||
text: filter,
|
||||
}))
|
||||
})
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
export default function UserController() {
|
||||
export const UserController = () => {
|
||||
const [users, setUsers] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [columns, setColumns] = useState([])
|
||||
@ -104,7 +58,7 @@ export default function UserController() {
|
||||
const users = arrayOrDefault(await AdminUserService.getAll())
|
||||
setUsers(users)
|
||||
|
||||
const filters = makeDataFilters(users, ['surname', 'name', 'patronymic', 'email'])
|
||||
const filters = makeTextFilters(users, ['surname', 'name', 'patronymic', 'email'])
|
||||
|
||||
|
||||
setColumns([
|
||||
@ -130,7 +84,7 @@ export default function UserController() {
|
||||
sorter: makeStringSorter('surname'),
|
||||
filters: filters.surname,
|
||||
filterSearch: true,
|
||||
onFilter: makeOnFilter('surname'),
|
||||
onFilter: makeTextOnFilter('surname'),
|
||||
}),
|
||||
makeColumn('Имя', 'name', {
|
||||
editable: true,
|
||||
@ -138,7 +92,7 @@ export default function UserController() {
|
||||
sorter: makeStringSorter('name'),
|
||||
filters: filters.name,
|
||||
filterSearch: true,
|
||||
onFilter: makeOnFilter('name'),
|
||||
onFilter: makeTextOnFilter('name'),
|
||||
}),
|
||||
makeColumn('Отчество', 'patronymic', {
|
||||
editable: true,
|
||||
@ -146,7 +100,7 @@ export default function UserController() {
|
||||
sorter: makeStringSorter('patronymic'),
|
||||
filters: filters.patronymic,
|
||||
filterSearch: true,
|
||||
onFilter: makeOnFilter('patronymic'),
|
||||
onFilter: makeTextOnFilter('patronymic'),
|
||||
}),
|
||||
makeColumn('E-mail', 'email', {
|
||||
editable: true,
|
||||
@ -154,7 +108,7 @@ export default function UserController() {
|
||||
sorter: makeStringSorter('email'),
|
||||
filters: filters.email,
|
||||
filterSearch: true,
|
||||
onFilter: makeOnFilter('email'),
|
||||
onFilter: makeTextOnFilter('email'),
|
||||
}),
|
||||
makeColumn('Номер телефона', 'phone', {
|
||||
editable: true,
|
||||
@ -204,7 +158,7 @@ export default function UserController() {
|
||||
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||
additionalButtons={additionalButtons}
|
||||
buttonsWidth={120}
|
||||
pagination={{ defaultPageSize: 14 }}
|
||||
pagination={defaultPagination}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
<ChangePassword
|
||||
@ -216,3 +170,5 @@ export default function UserController() {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserController
|
@ -2,7 +2,7 @@ import { memo, useEffect, useState } from 'react'
|
||||
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { makeColumn, makeDateSorter, makeStringSorter, Table } from '../../components/Table'
|
||||
import { defaultPagination, makeColumn, makeDateSorter, makeStringSorter, Table } from '../../components/Table'
|
||||
import { RequerstTrackerService } from '../../services/api'
|
||||
import { arrayOrDefault, formatDate } from '../../utils'
|
||||
|
||||
@ -17,11 +17,6 @@ const columns = [
|
||||
}),
|
||||
]
|
||||
|
||||
const pagination = {
|
||||
defaultPageSize: 16,
|
||||
showSizeChanger: true,
|
||||
}
|
||||
|
||||
export const VisitLog = memo(() => {
|
||||
const [logData, setLogData] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@ -44,7 +39,7 @@ export const VisitLog = memo(() => {
|
||||
bordered
|
||||
columns={columns}
|
||||
dataSource={logData}
|
||||
pagination={pagination}
|
||||
pagination={defaultPagination}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
)
|
||||
|
28
src/pages/AdminPanel/WellController/TelemetrySelect.jsx
Normal file
28
src/pages/AdminPanel/WellController/TelemetrySelect.jsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { memo } from 'react'
|
||||
import { Select } from 'antd'
|
||||
|
||||
import { getTelemetryLabel } from '../../../components/Views'
|
||||
|
||||
export const TelemetrySelect = memo(({ telemetry, value, onChange }) => {
|
||||
const onSelectChange = (id) => {
|
||||
onChange?.(telemetry.find((row) => row.id === id))
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={value?.id}
|
||||
onChange={onSelectChange}
|
||||
dropdownClassName={'telemetry_select'}
|
||||
>
|
||||
{telemetry.map((row, i) => (
|
||||
<Select.Option key={i} value={row.id}>
|
||||
<span className={row?.info?.well ? 'telemetry_used' : 'telemetry_unused'}>
|
||||
{getTelemetryLabel(row)}
|
||||
</span>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
)
|
||||
})
|
||||
|
||||
export default TelemetrySelect
|
@ -1,10 +1,10 @@
|
||||
import { Button, Select } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button } from 'antd'
|
||||
import { CopyOutlined } from '@ant-design/icons'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import { TelemetryView, getTelemetryLabel, CompanyView } from '../../components/Views'
|
||||
import LoaderPortal from '../../../components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '../../../components/factory'
|
||||
import { TelemetryView, CompanyView } from '../../../components/Views'
|
||||
import {
|
||||
EditableTable,
|
||||
makeColumn,
|
||||
@ -13,46 +13,27 @@ import {
|
||||
makeStringSorter,
|
||||
makeNumericSorter,
|
||||
makeTagColumn,
|
||||
} from '../../components/Table'
|
||||
defaultPagination,
|
||||
} from '../../../components/Table'
|
||||
import {
|
||||
AdminClusterService,
|
||||
AdminCompanyService,
|
||||
AdminTelemetryService,
|
||||
AdminWellService,
|
||||
} from '../../services/api'
|
||||
import { arrayOrDefault } from '../../utils'
|
||||
import { coordsFixed } from './DepositController'
|
||||
} from '../../../services/api'
|
||||
import { arrayOrDefault } from '../../../utils'
|
||||
|
||||
import '../../styles/admin.css'
|
||||
import { coordsFixed } from '../DepositController'
|
||||
import TelemetrySelect from './TelemetrySelect'
|
||||
|
||||
import '../../../styles/admin.css'
|
||||
|
||||
const wellTypes = [
|
||||
{ value: 1, label: 'Наклонно-направленная' },
|
||||
{ value: 2, label: 'Горизонтальная' },
|
||||
]
|
||||
|
||||
const TelemetrySelect = memo(({ telemetry, value, onChange }) => {
|
||||
const onSelectChange = (id) => {
|
||||
onChange?.(telemetry.find((row) => row.id === id))
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={value?.id}
|
||||
onChange={onSelectChange}
|
||||
dropdownClassName={'telemetry_select'}
|
||||
>
|
||||
{telemetry.map((row, i) => (
|
||||
<Select.Option key={i} value={row.id}>
|
||||
<span className={row?.info?.well ? 'telemetry_used' : 'telemetry_unused'}>
|
||||
{getTelemetryLabel(row)}
|
||||
</span>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
)
|
||||
})
|
||||
|
||||
export default function WellController() {
|
||||
export const WellController = () => {
|
||||
const [columns, setColumns] = useState([])
|
||||
const [wells, setWells] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
@ -142,6 +123,7 @@ export default function WellController() {
|
||||
bordered
|
||||
columns={columns}
|
||||
dataSource={wells}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={makeActionHandler('insert', handlerProps, recordParser)}
|
||||
onRowEdit={makeActionHandler('put', handlerProps, recordParser)}
|
||||
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||
@ -151,3 +133,5 @@ export default function WellController() {
|
||||
</LoaderPortal>
|
||||
)
|
||||
}
|
||||
|
||||
export default WellController
|
30
src/utils/table.ts
Normal file
30
src/utils/table.ts
Normal file
@ -0,0 +1,30 @@
|
||||
export const makeTextOnFilter = (key: string) =>
|
||||
(value: string, record?: Record<string, string>) => record?.[key]?.startsWith(value)
|
||||
|
||||
export const makeTextFilters = (array: Record<string, unknown>[], keys: string[]) => {
|
||||
const filters: string[][] = Array(keys.length)
|
||||
|
||||
filters.forEach((_, idx) => filters[idx] = [])
|
||||
|
||||
array.forEach((row) => {
|
||||
if (!row) return
|
||||
keys.forEach((key, idx) => {
|
||||
if (!row[key]) return
|
||||
const value = String(row[key])
|
||||
if (filters[idx].indexOf(value) < 0)
|
||||
filters[idx].push(value)
|
||||
})
|
||||
})
|
||||
|
||||
const out: Record<string, { value: string, text: string }[]> = {}
|
||||
|
||||
keys.forEach((key, idx) => {
|
||||
filters[idx].sort()
|
||||
out[key] = filters[idx].map((filter) => ({
|
||||
value: filter,
|
||||
text: filter,
|
||||
}))
|
||||
})
|
||||
|
||||
return out
|
||||
}
|
Loading…
Reference in New Issue
Block a user