Добавлена view для разрешений

К отображению телеметрии добавлено id
Актуализирован редактор ролей
Добавлена кнопка смены пароля
Окно смены пароля вынесено в компонент
This commit is contained in:
Александр Сироткин 2021-12-27 18:06:26 +05:00
parent 1b17ee2cfd
commit e084727c72
11 changed files with 380 additions and 262 deletions

View File

@ -0,0 +1,63 @@
import { memo, useState } from 'react'
import { useForm } from 'antd/lib/form/Form'
import { Form, Input, Modal, FormProps } from 'antd'
import { AuthService } from '../services/api'
import { passwordRules } from '../utils/validationRules'
import LoaderPortal from './LoaderPortal'
import { invokeWebApiWrapperAsync } from './factory'
const formLayout: FormProps = { labelCol: { span: 11 }, wrapperCol: { span: 16 } }
export type ChangePasswordProps = {
userId?: number
visible?: boolean
onCancel?: () => void
onOk?: () => void
}
export const ChangePassword = memo<ChangePasswordProps>(({ userId, visible, onCancel, onOk }) => {
const [showLoader, setShowLoader] = useState<boolean>(false)
const [password, setPassword] = useState<string>('')
const [form] = useForm()
const onModalCancel = () => {
form.resetFields()
onCancel?.()
}
const onFormFinish = () => invokeWebApiWrapperAsync(
async() => {
await AuthService.changePassword(userId ?? localStorage['userId'], `"${password}"`)
onOk?.()
},
setShowLoader,
`Не удалось сменить пароль пользователя ${localStorage['login']}`
)
return (
<Modal
centered
title={'Сменить пароль'}
visible={visible}
onCancel={onModalCancel}
onOk={() => form.submit()}
>
<LoaderPortal show={showLoader}>
<Form
{...formLayout}
form={form}
name={'change-password'}
onFinish={onFormFinish}
>
<Form.Item label={'Новый пароль'} name={'new-password'} rules={passwordRules}>
<Input.Password onChange={(e) => setPassword(e.target.value)} value={password} />
</Form.Item>
</Form>
</LoaderPortal>
</Modal>
)
})

View File

@ -49,6 +49,8 @@ export const EditableTable = ({
onRowAdd, // Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается onRowAdd, // Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается
onRowEdit, // Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается onRowEdit, // Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается
onRowDelete, // Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается onRowDelete, // Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается
additionalButtons,
buttonsWidth,
...otherTableProps ...otherTableProps
}) => { }) => {
@ -144,7 +146,7 @@ export const EditableTable = ({
} }
const operationColumn = { const operationColumn = {
width: 82, width: buttonsWidth ?? 82,
title: !!onRowAdd && ( title: !!onRowAdd && (
<Button <Button
onClick={addNewRow} onClick={addNewRow}
@ -157,6 +159,7 @@ export const EditableTable = ({
<span> <span>
<Button onClick={() => save(record)} icon={<SaveOutlined/>}/> <Button onClick={() => save(record)} icon={<SaveOutlined/>}/>
<Button onClick={cancel} icon={<CloseCircleOutlined/>}/> <Button onClick={cancel} icon={<CloseCircleOutlined/>}/>
{additionalButtons?.(record, editingKey)}
</span> </span>
) : ( ) : (
<span> <span>
@ -172,6 +175,7 @@ export const EditableTable = ({
<Button icon={<DeleteOutlined/>}/> <Button icon={<DeleteOutlined/>}/>
</Popconfirm> </Popconfirm>
)} )}
{additionalButtons?.(record, editingKey)}
</span> </span>
), ),
} }

View File

@ -1,9 +1,10 @@
import { ReactNode } from 'react' import { memo, useEffect, useState, ReactNode } from 'react'
import { InputNumber, Select, Table as RawTable } from 'antd' import { InputNumber, Select, Table as RawTable, Tag, SelectProps } from 'antd'
import { OptionsType } from 'rc-select/lib/interface' import { OptionsType } from 'rc-select/lib/interface'
import { tryAddKeys } from './EditableTable' import { tryAddKeys } from './EditableTable'
import { makeNumericSorter, makeStringSorter } from './sorters' import { makeNumericSorter, makeStringSorter } from './sorters'
import { Rule } from 'rc-field-form/lib/interface' import { Rule } from 'rc-field-form/lib/interface'
import { SelectValue } from 'antd/lib/select'
export { makeDateSorter, makeNumericSorter, makeStringSorter } from './sorters' export { makeDateSorter, makeNumericSorter, makeStringSorter } from './sorters'
export { EditableTable, makeActionHandler } from './EditableTable' export { EditableTable, makeActionHandler } from './EditableTable'
export { DatePickerWrapper } from './DatePickerWrapper' export { DatePickerWrapper } from './DatePickerWrapper'
@ -205,13 +206,71 @@ export const makeSelectColumn = <T extends unknown = string>(
dataIndex: string, dataIndex: string,
options: OptionsType, options: OptionsType,
defaultValue?: T, defaultValue?: T,
other?: columnPropsOther other?: columnPropsOther,
selectOther?: SelectProps<SelectValue>
) => makeColumn(title, dataIndex, { ) => makeColumn(title, dataIndex, {
input: <Select options={options}/>, input: <Select options={options} {...selectOther}/>,
render: (value) => options?.find(option => option?.value === value)?.label ?? defaultValue ?? value ?? '--', render: (value) => options?.find(option => option?.value === value)?.label ?? defaultValue ?? value ?? '--',
...other ...other
}) })
const makeTagInput = <T extends Record<string, any>>(id_key: string, value_key: string) => memo<{
options: T[],
value?: T[],
onChange?: (values: T[]) => void
}>(({ options, value, onChange }) => {
const [selectOptions, setSelectOptions] = useState<OptionsType>([])
useEffect(() => {
setSelectOptions(options.map((elm) => ({
value: elm[id_key],
label: elm[value_key]
})))
}, [options])
const onSelectChange = (rawValues?: SelectValue) => {
let values: string[] = []
if (typeof rawValues === 'string')
values = rawValues.split(',')
else if (typeof rawValues === 'number')
values = [`${rawValues}`]
const objectValues: T[] = values.reduce((out: T[], id: string) => {
const res = options.find((option) => `${option[id_key]}` === id)
if (res) out.push(res)
return out
}, [])
onChange?.(objectValues)
}
return (
<Select
mode={'tags'}
options={selectOptions}
value={value?.join(',')}
onChange={onSelectChange}
/>
)
})
export const makeTagColumn = <T extends Record<string, any>>(
title: string,
dataIndex: string,
options: T[],
value_key: string,
label_key: string,
other?: columnPropsOther
) => {
const InputComponent = makeTagInput<T>(value_key, label_key)
return makeColumn(title, dataIndex, {
...other,
render: (item?: T[]) => item?.map((elm: T) => <Tag key={elm[label_key]} color='blue'>{elm[label_key]}</Tag>) ?? '-',
input: <InputComponent options={options} />,
})
}
type PaginationContainer = { type PaginationContainer = {
skip?: number skip?: number
take?: number take?: number

View File

@ -1,52 +1,35 @@
import { MouseEventHandler, useState } from 'react' import { MouseEventHandler, useState } from 'react'
import { Link, useHistory } from 'react-router-dom' import { Link, useHistory } from 'react-router-dom'
import { Button, Dropdown, Menu, Modal, Form, Input, FormProps } from 'antd' import { Button, Dropdown, Menu } from 'antd'
import { useForm } from 'antd/lib/form/Form'
import { UserOutlined } from '@ant-design/icons' import { UserOutlined } from '@ant-design/icons'
import { AuthService } from '../services/api'
import { passwordRules } from '../utils/validationRules'
import { invokeWebApiWrapperAsync } from './factory'
import { PrivateMenuItem } from './Private' import { PrivateMenuItem } from './Private'
import LoaderPortal from './LoaderPortal' import { ChangePassword } from './ChangePassword'
const handleLogout = () => { const handleLogout = () => {
localStorage.removeItem('login') localStorage.removeItem('login')
localStorage.removeItem('token') localStorage.removeItem('token')
} }
const formLayout: FormProps = { labelCol: { span: 11 }, wrapperCol: { span: 16 } }
type UserMenuProps = { type UserMenuProps = {
isAdmin?: boolean isAdmin?: boolean
} }
export const UserMenu: React.FC<UserMenuProps> = ({ isAdmin }) => { export const UserMenu: React.FC<UserMenuProps> = ({ isAdmin }) => {
const [form] = useForm()
const [isModalVisible, setIsModalVisible] = useState<boolean>(false) const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
const [showLoader, setShowLoader] = useState<boolean>(false)
const [password, setPassword] = useState<string>('')
const history = useHistory() const history = useHistory()
const changePassword = () => invokeWebApiWrapperAsync(
async() => {
await AuthService.changePassword(localStorage['userId'], `"${password}"`)
history.push('/login')
},
setShowLoader,
`Не удалось сменить пароль пользователя ${localStorage['login']}`
)
const onFormCancel = () => {
form.resetFields()
setIsModalVisible(false)
}
const onChangePasswordClick: MouseEventHandler = (e) => { const onChangePasswordClick: MouseEventHandler = (e) => {
setIsModalVisible(true) setIsModalVisible(true)
e.preventDefault() e.preventDefault()
} }
const onChangePasswordOk = () => {
setIsModalVisible(false)
history.push('/login')
}
return ( return (
<> <>
<Dropdown <Dropdown
@ -71,26 +54,11 @@ export const UserMenu: React.FC<UserMenuProps> = ({ isAdmin }) => {
> >
<Button icon={<UserOutlined/>}>{localStorage['login']}</Button> <Button icon={<UserOutlined/>}>{localStorage['login']}</Button>
</Dropdown> </Dropdown>
<Modal <ChangePassword
title={'Сменить пароль'}
centered
visible={isModalVisible} visible={isModalVisible}
onCancel={onFormCancel} onOk={onChangePasswordOk}
onOk={() => form.submit()} onCancel={() => setIsModalVisible(false)}
> />
<LoaderPortal show={showLoader}>
<Form
{...formLayout}
form={form}
name={'change-password'}
onFinish={changePassword}
>
<Form.Item label={'Новый пароль'} name={'new-password'} rules={passwordRules}>
<Input.Password onChange={(e) => setPassword(e.target.value)} value={password} />
</Form.Item>
</Form>
</LoaderPortal>
</Modal>
</> </>
) )
} }

View File

@ -0,0 +1,27 @@
import { Tooltip } from 'antd'
import { memo } from 'react'
import { PermissionDto } from '../../services/api'
import { Grid, GridItem } from '../Grid'
export type PermissionViewProps = {
info?: PermissionDto
}
export const PermissionView = memo<PermissionViewProps>(({ info }) => info ? (
<Tooltip overlayInnerStyle={{ width: '400px' }} title={
<Grid>
<GridItem row={1} col={1}>Название:</GridItem>
<GridItem row={1} col={2}>{info.name}</GridItem>
<GridItem row={2} col={1}>Описание:</GridItem>
<GridItem row={2} col={2}>{info.description}</GridItem>
</Grid>
}>
{info.name}
</Tooltip>
) : (
<Tooltip title={'нет данных'}>-</Tooltip>
))
export default PermissionView

View File

@ -1,6 +1,6 @@
import { memo } from 'react' import { Fragment, memo } from 'react'
import { Tooltip } from 'antd' import { Tooltip } from 'antd'
import { TelemetryInfoDto } from '../../services/api' import { TelemetryDto, TelemetryInfoDto } from '../../services/api'
import { Grid, GridItem } from '../Grid' import { Grid, GridItem } from '../Grid'
const lables: { [labelKey: string]: string } = { const lables: { [labelKey: string]: string } = {
@ -17,25 +17,29 @@ const lables: { [labelKey: string]: string } = {
spinPlcVersion: 'Версия Спин Мастер', spinPlcVersion: 'Версия Спин Мастер',
} }
export const getTelemetryLabel = (info?: TelemetryInfoDto) => info ? `${info.deposit} / ${info.cluster} / ${info.well}` : '---' export const getTelemetryLabel = (telemetry?: TelemetryDto) =>
`${telemetry?.id ?? '-'} / ${telemetry?.info?.deposit ?? '-'} / ${telemetry?.info?.cluster ?? '-'} / ${telemetry?.info?.well ?? '-'}`
export type TelemetryViewProps = { export type TelemetryViewProps = {
info?: TelemetryInfoDto telemetry?: TelemetryDto
} }
export const TelemetryView = memo<TelemetryViewProps>(({ info }) => info ? ( export const TelemetryView = memo<TelemetryViewProps>(({ telemetry }) => telemetry?.info ? (
<Tooltip overlayInnerStyle={{ width: '400px' }} title={ <Tooltip
<Grid> overlayInnerStyle={{ width: '400px' }}
{(Object.keys(info) as Array<keyof TelemetryInfoDto>).map((key, i) => ( title={
<> <Grid>
<GridItem row={i+1} col={1}>{lables[key] ?? key}:</GridItem> {(Object.keys(telemetry.info) as Array<keyof TelemetryInfoDto>).map((key, i) => (
<GridItem row={i+1} col={2}>{info[key]}</GridItem> <Fragment key={i}>
</> <GridItem row={i+1} col={1}>{lables[key] ?? key}:</GridItem>
))} <GridItem row={i+1} col={2}>{telemetry.info?.[key]}</GridItem>
</Grid> </Fragment>
}> ))}
{getTelemetryLabel(info)} </Grid>
}
>
{getTelemetryLabel(telemetry)}
</Tooltip> </Tooltip>
) : ( ) : (
<Tooltip title={'нет данных'}>-</Tooltip> <Tooltip title={'нет данных'}>{getTelemetryLabel()}</Tooltip>
)) ))

View File

@ -1,9 +1,11 @@
export type { CompanyViewProps } from './CompanyView' export type { CompanyViewProps } from './CompanyView'
export type { MarkViewProps } from './MarkView' export type { MarkViewProps } from './MarkView'
export type { PermissionViewProps } from './PermissionView'
export type { TelemetryViewProps } from './TelemetryView' export type { TelemetryViewProps } from './TelemetryView'
export type { UserViewProps } from './UserView' export type { UserViewProps } from './UserView'
export { CompanyView } from './CompanyView' export { CompanyView } from './CompanyView'
export { MarkView } from './MarkView' export { MarkView } from './MarkView'
export { TelemetryView } from './TelemetryView' export { PermissionView } from './PermissionView'
export { TelemetryView, getTelemetryLabel } from './TelemetryView'
export { UserView } from './UserView' export { UserView } from './UserView'

View File

@ -1,101 +1,50 @@
import { Button, Modal } from 'antd' import { Select, Tag } from 'antd'
import { useEffect, useState } from 'react' import { memo, useEffect, useState } from 'react'
import { AdminUserRoleService } from '../../services/api'
import LoaderPortal from '../../components/LoaderPortal' import LoaderPortal from '../../components/LoaderPortal'
import { PermissionView } from '../../components/Views'
import { invokeWebApiWrapperAsync } from '../../components/factory' import { invokeWebApiWrapperAsync } from '../../components/factory'
import { EditableTable, makeActionHandler, makeColumn, makeSelectColumn } from '../../components/Table' import { EditableTable, makeActionHandler, makeColumn, makeSelectColumn } from '../../components/Table'
import { AdminPermissionService, AdminUserRoleService } from '../../services/api'
import { arrayOrDefault } from '../../utils'
export const toHexString = (num, size) => '0x' + ('0'.repeat(size) + num.toString(16).toUpperCase()).slice(-size) const PermissionTag = memo(({ permissions, value, onChange }) => {
const [options, setOptions] = useState([])
const columns = [ useEffect(() => {
makeColumn('Имена прав', 'name', { setOptions(permissions.map((elm) => ({ key: Date.now(), value: `${elm.id}`, label: elm.name })))
width: 400, }, [permissions])
editable: true,
formItemRules: [{
required: true,
message: 'Пожалуйста, введите имя права'
}],
}),
]
export const RolePermissions = ({ value, onChange }) => { console.log({ permissions, value })
const [isModalVisible, setIsModalVisible] = useState(false)
const [list, setList] = useState([])
const save = () => { const onSelectChange = (values) => {
const newValue = list.map((value) => value.name) const arr = values.map((id) => permissions.find((elm) => `${elm.id}` === id))
if(!onChange(newValue)) onChange?.(arr)
setIsModalVisible(false)
} }
const add = (permission) => { const selectValue = value?.map((val) => `${val.id}`)
permission.key = Date.now()
setList((prevList) => {
if (!prevList) prevList = []
prevList.push(permission)
return prevList
})
}
const edit = (permission) => {
if (!permission.key) return
const idx = list.findIndex(v => v.key === permission.key)
if (idx < 0) return
setList((prevList) => {
prevList[idx] = permission
return prevList
})
}
const remove = (permission) => {
if (!permission.key) return
const idx = list.findIndex(v => v.key === permission.key)
if (idx < 0) return
setList((prevList) => {
prevList.splice(idx, 1)
return prevList
})
}
return ( return (
<> <Select
<Button type={'link'} onClick={() => setIsModalVisible(true)}>Редактировать</Button> showSearch
<Modal mode={'tags'}
title={'Права доступа'} options={options}
centered value={selectValue}
visible={isModalVisible} onChange={onSelectChange}
width={750} />
onCancel={() => setIsModalVisible(false)}
onOk={save}
okText={'Сохранить'}
>
<EditableTable
size={'small'}
bordered
columns={columns}
dataSource={list}
onRowAdd={add}
onRowEdit={edit}
onRowDelete={remove}
/>
</Modal>
</>
) )
} })
export const RoleController = () => { export const RoleController = () => {
const [permissions, setPermissions] = useState([])
const [roles, setRoles] = useState([]) const [roles, setRoles] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
const [columns, setColumns] = useState([]) const [columns, setColumns] = useState([])
const updateTable = () => invokeWebApiWrapperAsync( const loadRoles = async () => {
async () => { const roles = await AdminUserRoleService.getAll()
const roles = await AdminUserRoleService.getAll() setRoles(Array.isArray(roles) ? roles : [])
setRoles(roles) }
},
setShowLoader,
`Не удалось загрузить список прав`
)
useEffect(() => { useEffect(() => {
const options = roles?.map((r) => ({ value: r.id, label: r.caption })) ?? [] const options = roles?.map((r) => ({ value: r.id, label: r.caption })) ?? []
@ -104,23 +53,39 @@ export const RoleController = () => {
makeSelectColumn('Роль-родитель', 'idParent', options, options[0], { makeSelectColumn('Роль-родитель', 'idParent', options, options[0], {
width: 200, width: 200,
editable: true editable: true
}), }, { allowClear: true }),
makeColumn('Права доступа', 'permissions', { makeColumn('Права доступа', 'permissions', {
width: 200, width: 200,
editable: true, editable: true,
input: <RolePermissions />, input: <PermissionTag permissions={permissions} />,
render: (permissions) => permissions?.join(', ') ?? '', render: (item) => item?.map((elm) => (
}) <Tag key={elm.name} color={'blue'}>
<PermissionView info={elm} />
</Tag>
)) ?? '-',
}),
]) ])
}, [roles]) }, [roles, permissions])
useEffect(updateTable, []) useEffect(() => invokeWebApiWrapperAsync(
async () => {
const permissions = await AdminPermissionService.getAll()
setPermissions(arrayOrDefault(permissions))
await loadRoles()
},
setShowLoader,
`Не удалось загрузить список прав`
), [])
const handlerProps = { const handlerProps = {
service: AdminUserRoleService, service: AdminUserRoleService,
setLoader: setShowLoader, setLoader: setShowLoader,
errorMsg: `Не удалось выполнить операцию`, errorMsg: `Не удалось выполнить операцию`,
onComplete: updateTable onComplete: async () => invokeWebApiWrapperAsync(
loadRoles,
setShowLoader,
`Не удалось загрузить список прав`
)
} }
return ( return (

View File

@ -1,66 +1,28 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { UserSwitchOutlined } from '@ant-design/icons'
import { invokeWebApiWrapperAsync } from '../../components/factory' import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal' import LoaderPortal from '../../components/LoaderPortal'
import { EditableTable, makeColumn, makeSelectColumn, makeActionHandler, makeStringSorter, makeNumericSorter } from '../../components/Table' import { EditableTable, makeColumn, makeSelectColumn, makeActionHandler, makeStringSorter, makeNumericSorter } from '../../components/Table'
import { AdminCompanyService, AdminUserService } from '../../services/api' import { AdminCompanyService, AdminUserService } from '../../services/api'
import { createLoginRules, nameRules, phoneRules, emailRules } from '../../utils/validationRules' import { createLoginRules, nameRules, phoneRules, emailRules } from '../../utils/validationRules'
import { arrayOrDefault } from '../../utils' import { arrayOrDefault } from '../../utils'
import { Button } from 'antd'
import { ChangePassword } from '../../components/ChangePassword'
export default function UserController() { export default function UserController() {
const [companies, setCompanies] = useState([])
const [users, setUsers] = useState([]) const [users, setUsers] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
const [columns, setColumns] = useState([])
const [selectedId, setSelectedId] = useState(null)
const userColumns = [ const additionalButtons = (record, editingKey) => (
makeColumn('Логин', 'login', { <Button
editable: true, icon={<UserSwitchOutlined />}
formItemRules: [ onClick={() => setSelectedId(record.id)}
{ required: true }, title={'Сменить пароль'}
...createLoginRules, disabled={editingKey !== ''}
() => ({ />
validator(_, value) { )
if (!value || users.findIndex((user) => user.login === value) < 0)
return Promise.resolve()
return Promise.reject(new Error('Логин уже занят!'))
}
})
],
sorter: makeStringSorter('login'),
}),
makeColumn('Фамилия', 'surname', {
editable: true,
formItemRules: [{ required: true }, ...nameRules],
sorter: makeStringSorter('surname'),
}),
makeColumn('Имя', 'name', {
editable: true,
formItemRules: nameRules,
sorter: makeStringSorter('name'),
}),
makeColumn('Отчество', 'patronymic', {
editable: true,
formItemRules: nameRules,
sorter: makeStringSorter('patronymic'),
}),
makeColumn('E-mail', 'email', {
editable: true,
formItemRules: [{ required: true }, ...emailRules],
sorter: makeStringSorter('email'),
}),
makeColumn('Номер телефона', 'phone', {
editable: true,
formItemRules: phoneRules,
sorter: makeStringSorter('phone'),
}),
makeColumn('Должность', 'position', {
editable: true,
sorter: makeStringSorter('position'),
}),
makeSelectColumn('Компания', 'idCompany', companies, '--', {
editable: true,
sorter: makeNumericSorter('idCompany'),
}),
]
const updateTable = () => invokeWebApiWrapperAsync( const updateTable = () => invokeWebApiWrapperAsync(
async() => { async() => {
@ -75,14 +37,65 @@ export default function UserController() {
async () => { async () => {
let companies = arrayOrDefault(await AdminCompanyService.getAll()) let companies = arrayOrDefault(await AdminCompanyService.getAll())
companies = companies?.map((company) => ({ value: company.id, label: company.caption })) companies = companies?.map((company) => ({ value: company.id, label: company.caption }))
setCompanies(companies)
const users = arrayOrDefault(await AdminUserService.getAll())
setUsers(users)
setColumns([
makeColumn('Логин', 'login', {
editable: true,
formItemRules: [
{ required: true },
...createLoginRules,
() => ({
validator(_, value) {
if (!value || users.findIndex((user) => user.login === value) < 0)
return Promise.resolve()
return Promise.reject(new Error('Логин уже занят!'))
}
})
],
sorter: makeStringSorter('login'),
}),
makeColumn('Фамилия', 'surname', {
editable: true,
formItemRules: [{ required: true }, ...nameRules],
sorter: makeStringSorter('surname'),
}),
makeColumn('Имя', 'name', {
editable: true,
formItemRules: nameRules,
sorter: makeStringSorter('name'),
}),
makeColumn('Отчество', 'patronymic', {
editable: true,
formItemRules: nameRules,
sorter: makeStringSorter('patronymic'),
}),
makeColumn('E-mail', 'email', {
editable: true,
formItemRules: [{ required: true }, ...emailRules],
sorter: makeStringSorter('email'),
}),
makeColumn('Номер телефона', 'phone', {
editable: true,
formItemRules: phoneRules,
sorter: makeStringSorter('phone'),
}),
makeColumn('Должность', 'position', {
editable: true,
sorter: makeStringSorter('position'),
}),
makeSelectColumn('Компания', 'idCompany', companies, '--', {
editable: true,
sorter: makeNumericSorter('idCompany'),
}),
])
}, },
setShowLoader, setShowLoader,
`Не удалось загрузить список компаний` `Не удалось загрузить список компаний`
), []) ), [])
useEffect(updateTable, [])
const handlerProps = { const handlerProps = {
service: AdminUserService, service: AdminUserService,
setLoader: setShowLoader, setLoader: setShowLoader,
@ -91,17 +104,27 @@ export default function UserController() {
} }
return ( return (
<LoaderPortal show={showLoader}> <>
<EditableTable <LoaderPortal show={showLoader}>
size={'small'} <EditableTable
bordered size={'small'}
columns={userColumns} bordered
dataSource={users} columns={columns}
onRowAdd={makeActionHandler('insert', handlerProps)} dataSource={users}
onRowEdit={makeActionHandler('update', handlerProps)} onRowAdd={makeActionHandler('insert', handlerProps)}
onRowDelete={makeActionHandler('delete', handlerProps)} onRowEdit={makeActionHandler('update', handlerProps)}
pagination={{ defaultPageSize: 14 }} onRowDelete={makeActionHandler('delete', handlerProps)}
additionalButtons={additionalButtons}
buttonsWidth={120}
pagination={{ defaultPageSize: 14 }}
/>
</LoaderPortal>
<ChangePassword
userId={selectedId}
visible={selectedId > 0}
onCancel={() => setSelectedId(null)}
onOk={() => setSelectedId(null)}
/> />
</LoaderPortal> </>
) )
} }

View File

@ -1,22 +1,25 @@
import { Select } from 'antd' import { Select } from 'antd'
import { memo, useEffect, useState } from 'react' import { memo, useEffect, useState } from 'react'
import { TelemetryView } from '../../components/Views'
import LoaderPortal from '../../components/LoaderPortal' import LoaderPortal from '../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory' import { invokeWebApiWrapperAsync } from '../../components/factory'
import { getTelemetryLabel } from '../../components/Views/TelemetryView' import { TelemetryView, getTelemetryLabel } from '../../components/Views'
import { import {
EditableTable, EditableTable,
makeColumn, makeColumn,
makeSelectColumn, makeSelectColumn,
makeActionHandler, makeActionHandler,
makeStringSorter, makeStringSorter,
makeNumericSorter makeNumericSorter,
makeTagColumn
} from '../../components/Table' } from '../../components/Table'
import { import {
AdminClusterService, AdminClusterService,
AdminCompanyService,
AdminTelemetryService, AdminTelemetryService,
AdminWellService AdminWellService
} from '../../services/api' } from '../../services/api'
import { arrayOrDefault } from '../../utils'
const wellTypes = [ const wellTypes = [
{ value: 1, label: 'Наклонно-направленная' }, { value: 1, label: 'Наклонно-направленная' },
@ -29,55 +32,27 @@ const TelemetrySelect = memo(({ telemetry, value, onChange }) => {
useEffect(() => { useEffect(() => {
const options = telemetry.map((row) => ({ const options = telemetry.map((row) => ({
value: row.id, value: row.id,
label: getTelemetryLabel(row.info) label: getTelemetryLabel(row)
})) }))
setOptions(options) setOptions(options)
}, [telemetry]) }, [telemetry])
const onSelectChange = (id) => { const onSelectChange = (id) => {
const value = telemetry.find((row) => row.id === id) onChange?.(telemetry.find((row) => row.id === id))
onChange?.(value)
} }
return <Select options={options} value={value?.id} onChange={onSelectChange}/> return <Select options={options} value={value?.id} onChange={onSelectChange}/>
}) })
export default function WellController() { export default function WellController() {
const [clusters, setClusters] = useState([]) const [columns, setColumns] = useState([])
const [wells, setWells] = useState([]) const [wells, setWells] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
const [telemetry, setTelemetry] = useState([])
const wellColumns = [ const updateTable = async () => invokeWebApiWrapperAsync(
makeSelectColumn('Куст', 'idCluster', clusters, '--', {
width: 200,
editable: true,
sorter: makeNumericSorter('idCluster'),
}),
makeColumn('Название', 'caption', {
width: 200,
editable: true,
sorter: makeStringSorter('caption'),
}),
makeSelectColumn('Тип', 'idWellType', wellTypes, '--', {
width: 150,
editable: true,
sorter: makeNumericSorter('idWellType'),
}),
makeColumn('Широта', 'latitude', { width: 150, editable: true }),
makeColumn('Долгота', 'longitude', { width: 150, editable: true }),
makeColumn('Телеметрия', 'telemetry', {
width: 150,
editable: true,
render: (telemetry) => <TelemetryView info={telemetry?.info} />,
input: <TelemetrySelect telemetry={telemetry}/>,
})
]
const updateTable = () => invokeWebApiWrapperAsync(
async () => { async () => {
const wells = await AdminWellService.getAll() const wells = await AdminWellService.getAll()
setWells(wells) setWells(arrayOrDefault(wells))
}, },
setShowLoader, setShowLoader,
`Не удалось загрузить список скважин` `Не удалось загрузить список скважин`
@ -85,13 +60,41 @@ export default function WellController() {
useEffect(() => invokeWebApiWrapperAsync( useEffect(() => invokeWebApiWrapperAsync(
async () => { async () => {
const telemetry = await AdminTelemetryService.getAll() const companies = arrayOrDefault(await AdminCompanyService.getAll())
setTelemetry(telemetry) const telemetry = arrayOrDefault(await AdminTelemetryService.getAll())
let clusters = arrayOrDefault(await AdminClusterService.getAll())
clusters = clusters.map((cluster) => ({ value: cluster.id, label: cluster.caption }))
setColumns([
makeSelectColumn('Куст', 'idCluster', clusters, '--', {
width: 200,
editable: true,
sorter: makeNumericSorter('idCluster'),
}),
makeColumn('Название', 'caption', {
width: 200,
editable: true,
sorter: makeStringSorter('caption'),
}),
makeSelectColumn('Тип', 'idWellType', wellTypes, '--', {
width: 150,
editable: true,
sorter: makeNumericSorter('idWellType'),
}),
makeColumn('Широта', 'latitude', { width: 150, editable: true }),
makeColumn('Долгота', 'longitude', { width: 150, editable: true }),
makeColumn('Телеметрия', 'telemetry', {
width: 150,
editable: true,
render: (telemetry) => <TelemetryView telemetry={telemetry} />,
input: <TelemetrySelect telemetry={telemetry}/>,
}),
makeTagColumn('Компании', 'companies', companies, 'id', 'caption', {
width: 400
}),
])
await updateTable() await updateTable()
let clusters = await AdminClusterService.getAll()
clusters = clusters?.map((cluster) => ({ value: cluster.id, label: cluster.caption }))
setClusters(clusters ?? [])
}, },
setShowLoader, setShowLoader,
`Не удалось загрузить список кустов` `Не удалось загрузить список кустов`
@ -109,7 +112,7 @@ export default function WellController() {
<EditableTable <EditableTable
size={'small'} size={'small'}
bordered bordered
columns={wellColumns} columns={columns}
dataSource={wells} dataSource={wells}
onRowAdd={makeActionHandler('insert', handlerProps)} onRowAdd={makeActionHandler('insert', handlerProps)}
onRowEdit={makeActionHandler('update', handlerProps)} onRowEdit={makeActionHandler('update', handlerProps)}

View File

@ -39,7 +39,7 @@ export const AdminPanel = () => {
<Link to={`${rootPath}/role`}>Роли</Link> <Link to={`${rootPath}/role`}>Роли</Link>
</PrivateMenuItem> </PrivateMenuItem>
<PrivateMenuItem key={'permission'} permission={'permission_editor'}> <PrivateMenuItem key={'permission'} permission={'permission_editor'}>
<Link to={`${rootPath}/permission`}>Права</Link> <Link to={`${rootPath}/permission`}>Разрешения</Link>
</PrivateMenuItem> </PrivateMenuItem>
<PrivateMenuItem key={'visit_log'}> <PrivateMenuItem key={'visit_log'}>
<Link to={`${rootPath}/visit_log`}>Журнал посещений</Link> <Link to={`${rootPath}/visit_log`}>Журнал посещений</Link>