Layout вынесен в отдельную директорию, добавлена админка, добавлена фабрика для колонок с Select

This commit is contained in:
goodmice 2021-12-02 15:21:10 +05:00
parent 2c585ab227
commit 7a3d26e846
14 changed files with 787 additions and 143 deletions

View File

@ -0,0 +1,21 @@
import { Button, Layout } from 'antd'
import { Link } from 'react-router-dom'
import PageHeader from '../PageHeader'
type LayoutPortalProps = {
title?: string
[props: string]: any
}
export const AdminLayoutPortal: React.FC<LayoutPortalProps> = ({title, ...props}) => (
<Layout.Content>
<PageHeader title={title}>
<Button size={'large'}>
<Link to={'/'}>Вернуться на основной сайт</Link>
</Button>
</PageHeader>
<Layout>
<Layout.Content className={'site-layout-background sheet'} {...props}/>
</Layout>
</Layout.Content>
)

View File

@ -0,0 +1,22 @@
import { Layout } from 'antd'
import PageHeader from '../PageHeader'
import WellTreeSelector from '../WellTreeSelector'
type LayoutPortalProps = {
title?: string
noSheet?: boolean
[props: string]: any
}
export const LayoutPortal: React.FC<LayoutPortalProps> = ({title, noSheet, ...props}) => (
<Layout.Content>
<PageHeader title={title}>
<WellTreeSelector />
</PageHeader>
<Layout>
{noSheet ? props.children : (
<Layout.Content className={'site-layout-background sheet'} {...props}/>
)}
</Layout>
</Layout.Content>
)

View File

@ -0,0 +1,2 @@
export { AdminLayoutPortal } from './AdminLayoutPortal'
export { LayoutPortal } from './LayoutPortal'

View File

@ -1,13 +0,0 @@
import {Layout} from 'antd'
import PageHeader from './PageHeader'
export const LayoutPortal = ({title, noSheet, ...props}) => (
<Layout.Content>
<PageHeader title={title}/>
<Layout>
{noSheet ? props.children : (
<Layout.Content className={'site-layout-background sheet'} {...props}/>
)}
</Layout>
</Layout.Content>
)

View File

@ -1,35 +1,24 @@
import { Layout, Button } from 'antd'
import { UserOutlined } from '@ant-design/icons'
import logo from '../images/logo_32.png'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import WellTreeSelector from './WellTreeSelector' import { Layout } from 'antd'
import { headerHeight } from '../utils' import { headerHeight } from '../utils'
import { UserMenu } from './UserMenu'
import logo from '../images/logo_32.png'
const { Header } = Layout type PageHeaderProps = {
children?: React.ReactNode
const logoStyle = { height: headerHeight } title?: string
const handleLogout = () => {
localStorage.removeItem('login')
localStorage.removeItem('token')
} }
type PageHeaderProps = { title?: string } export const PageHeader: React.FC<PageHeaderProps> = ({ children, title = 'Мониторинг' }) => (
export const PageHeader = ({ title = 'Мониторинг' }: PageHeaderProps) => (
<Layout> <Layout>
<Header className={'header'}> <Layout.Header className={'header'}>
<Link to={'/'} style={logoStyle}> <Link to={'/'} style={{ height: headerHeight }}>
<img src={logo} alt={'АСБ'} className={'logo'}/> <img src={logo} alt={'АСБ'} className={'logo'}/>
</Link> </Link>
<WellTreeSelector /> {children}
<h1 className={'title'}>{title}</h1> <h1 className={'title'}>{title}</h1>
<Link to={'/login'} onClick={handleLogout}> <UserMenu />
<Button icon={<UserOutlined/>}> </Layout.Header>
({localStorage['login']}) Выход
</Button>
</Link>
</Header>
</Layout> </Layout>
) )

View File

@ -1,5 +1,6 @@
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { Table as RawTable } from 'antd' import { Select, Table as RawTable } from 'antd'
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'
export { makeDateSorter, makeNumericSorter, makeStringSorter} from './sorters' export { makeDateSorter, makeNumericSorter, makeStringSorter} from './sorters'
@ -25,7 +26,7 @@ export const makeNumericRender = (fixed?: number) => (value: any, row: object):
) )
} }
export const makeNumericColumnOptions = (fixed?:number, sorterKey?:string ) => ({ export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string ) => ({
editable: true, editable: true,
initialValue: 0, initialValue: 0,
width:100, width:100,
@ -57,30 +58,36 @@ interface columnPropsOther {
formItemRules?: any[] formItemRules?: any[]
// дефолтное значение при добавлении новой строки // дефолтное значение при добавлении новой строки
initialValue?: string|number initialValue?: string|number
render?: (...attributes: any) => any
} }
export const makeColumn = (title:string | ReactNode, key:string, other?:columnPropsOther) => ({ export const makeColumn = (title: string | ReactNode, key: string, other?: columnPropsOther) => ({
title: title, title: title,
key: key, key: key,
dataIndex: key, dataIndex: key,
...other, ...other,
}) })
export const makeColumnsPlanFact = (title:string | ReactNode, key:string|string[], columsOther?:any|any[], gruopOther?:any) => export const makeColumnsPlanFact = (
{ title:string | ReactNode,
key:string | string[],
columsOther?: any | any[],
gruopOther?: any
) => {
let keyPlanLocal = key let keyPlanLocal = key
let keyFactLocal = key let keyFactLocal = key
if(key instanceof Array){ if (key instanceof Array) {
keyPlanLocal = key[0] keyPlanLocal = key[0]
keyFactLocal = key[1] keyFactLocal = key[1]
}else{ } else {
keyPlanLocal = key + 'Plan' keyPlanLocal = key + 'Plan'
keyFactLocal = key + 'Fact' keyFactLocal = key + 'Fact'
} }
let columsOtherLoacl :any[2] let columsOtherLoacl : any[2]
if(columsOther instanceof Array) if (columsOther instanceof Array)
columsOtherLoacl = [columsOther[0], columsOther[1]] columsOtherLoacl = [columsOther[0], columsOther[1]]
else else
columsOtherLoacl = [columsOther, columsOther] columsOtherLoacl = [columsOther, columsOther]
@ -109,7 +116,8 @@ export const makeTextColumn = (
filters: object[], filters: object[],
sorter?: (key: string) => any, sorter?: (key: string) => any,
render?: any, render?: any,
other?: any) => ({ other?: any
) => ({
title: title, title: title,
dataIndex: dataIndex, dataIndex: dataIndex,
key: dataIndex, key: dataIndex,
@ -120,9 +128,15 @@ export const makeTextColumn = (
...other ...other
}) })
export const makeNumericColumn = (title: string, dataIndex: string, export const makeNumericColumn = (
filters: object[], filterDelegate: (key: string | number) => any, title: string,
renderDelegate: (_: any, row: object) => any, width: string, other?: columnPropsOther) => ({ dataIndex: string,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string,
other?: columnPropsOther
) => ({
title: title, title: title,
dataIndex: dataIndex, dataIndex: dataIndex,
key: dataIndex, key: dataIndex,
@ -135,12 +149,17 @@ export const makeNumericColumn = (title: string, dataIndex: string,
...other ...other
}) })
export const makeNumericColumnPlanFact = (title: string, dataIndex: string, filters: object[], export const makeNumericColumnPlanFact = (
filterDelegate: (key: string | number) => any, renderDelegate: (_: any, row: object) => any, width: string) => title: string,
makeGroupColumn( title, [ dataIndex: string,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string
) => makeGroupColumn(title, [
makeNumericColumn('п', dataIndex + 'Plan', filters, filterDelegate, renderDelegate, width), makeNumericColumn('п', dataIndex + 'Plan', filters, filterDelegate, renderDelegate, width),
makeNumericColumn('ф', dataIndex + 'Fact', filters, filterDelegate, renderDelegate, width), makeNumericColumn('ф', dataIndex + 'Fact', filters, filterDelegate, renderDelegate, width),
]) ])
export const makeNumericStartEnd = ( export const makeNumericStartEnd = (
title: string, title: string,
@ -150,7 +169,7 @@ export const makeNumericStartEnd = (
filterDelegate: (key: string | number) => any, filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any, renderDelegate: (_: any, row: object) => any,
width: string, width: string,
) => makeGroupColumn( title, [ ) => makeGroupColumn(title, [
makeNumericColumn('старт', dataIndex + 'Start', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Start')), makeNumericColumn('старт', dataIndex + 'Start', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Start')),
makeNumericColumn('конец', dataIndex + 'End', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'End')) makeNumericColumn('конец', dataIndex + 'End', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'End'))
]) ])
@ -176,34 +195,43 @@ export const makeNumericAvgRange = (
filterDelegate: (key: string | number) => any, filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any, renderDelegate: (_: any, row: object) => any,
width: string width: string
) => makeGroupColumn( title, [ ) => makeGroupColumn(title, [
makeNumericColumn('мин', dataIndex + 'Min', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Min')), makeNumericColumn('мин', dataIndex + 'Min', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Min')),
makeNumericColumn('сред', dataIndex + 'Avg', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Avg')), makeNumericColumn('сред', dataIndex + 'Avg', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Avg')),
makeNumericColumn('макс', dataIndex + 'Max', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Max')) makeNumericColumn('макс', dataIndex + 'Max', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Max'))
]) ])
export const makeSelectColumn = <T extends unknown = string>(
title: string,
dataIndex: string,
options: OptionsType,
defaultValue?: T,
other?: columnPropsOther
) => makeColumn(title, dataIndex, {
input: <Select options={options}/>,
render: (key) => options.find(option => option?.key === key)?.label ?? defaultValue ?? '--',
...other
})
type PaginationContainer = { type PaginationContainer = {
skip?: number; skip?: number
take?: number; take?: number
count?: number; count?: number
items?: any[] | null; items?: any[] | null
} }
export const makePaginationObject = (paginationContainer:PaginationContainer, ...other:any) => { export const makePaginationObject = (сontainer: PaginationContainer, ...other: any) => ({
let page = 1 + Math.floor((paginationContainer.skip??0) /(paginationContainer.take??1));
return {
...other, ...other,
pageSize: paginationContainer.take, pageSize: сontainer.take,
total: paginationContainer.count ?? paginationContainer.items?.length ?? 0, total: сontainer.count ?? сontainer.items?.length ?? 0,
current: page, current: 1 + Math.floor((сontainer.skip ?? 0) / (сontainer.take ?? 1))
} })
}
interface TableContainer { interface TableContainer {
dataSource: any[]; dataSource: any[]
children?: any; children?: any
} }
export const Table = ({dataSource, children, ...other}: TableContainer) => { export const Table = ({dataSource, children, ...other}: TableContainer) => (
return <RawTable dataSource={tryAddKeys(dataSource)} {...other}>{children}</RawTable> <RawTable dataSource={tryAddKeys(dataSource)} {...other}>{children}</RawTable>
} )

View File

@ -0,0 +1,88 @@
import { MouseEventHandler, useState } from 'react'
import { Link, useHistory } from 'react-router-dom'
import { Button, Dropdown, Menu, Modal, Form, Input, FormProps } from 'antd'
import { useForm } from 'antd/lib/form/Form'
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 LoaderPortal from './LoaderPortal'
const handleLogout = () => {
localStorage.removeItem('login')
localStorage.removeItem('token')
}
const formLayout: FormProps = { labelCol: { span: 11 }, wrapperCol: { span: 16 } }
export const UserMenu: React.FC = () => {
const [form] = useForm()
const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
const [showLoader, setShowLoader] = useState<boolean>(false)
const [password, setPassword] = useState<string>('')
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) => {
setIsModalVisible(true)
e.preventDefault()
}
return (
<>
<Dropdown
placement={'bottomRight'}
overlay={(
<Menu style={{ textAlign: 'right' }}>
<PrivateMenuItem roles={['admin']}>
<Link to={'/admin'} onClick={() => {}}>Панель администратора</Link>
</PrivateMenuItem>
<Menu.Item>
<Link to={'/'} onClick={onChangePasswordClick}>Сменить пароль</Link>
</Menu.Item>
<Menu.Item>
<Link to={'/login'} onClick={handleLogout}>Выход</Link>
</Menu.Item>
</Menu>
)}
>
<Button icon={<UserOutlined/>}>{localStorage['login']}</Button>
</Dropdown>
<Modal
title={'Сменить пароль'}
centered
visible={isModalVisible}
onCancel={onFormCancel}
onOk={() => form.submit()}
>
<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,89 @@
import { useEffect, useState } from 'react'
import { useHistory, useParams } from 'react-router'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { EditableTable, makeColumn, makeNumericColumn, makeSelectColumn } from '../../components/Table'
import { AdminClusterService, DepositService } from '../../services/api'
export const ClusterController = () => {
const { page } = useParams()
const history = useHistory()
const [deposits, setDeposits] = useState([])
const [clusters, setClusters] = useState([])
const [pagination, setPagination] = useState({ current: page ?? 1, pageSize: 20 })
const [pagintaionTotal, setPagintaionTotal] = useState(0)
const [showLoader, setShowLoader] = useState(false)
const clusterColumns = [
makeSelectColumn('Месторождение', 'idDeposit', deposits, '--', { width: 200, editable: true }),
makeColumn('Название', 'caption', { width: 200, editable: true }),
makeColumn('Описание', 'description', { width: 500, editable: true }),
makeNumericColumn('Широта', 'latitude', null, null, null, 150, { editable: true }),
makeNumericColumn('Долгота', 'longitude', null, null, null, 150, { editable: true })
]
const updateTable = () => invokeWebApiWrapperAsync(
async () => {
history.push(`/admin/cluster/${pagination.current}`)
const skip = ((pagination.current - 1) * pagination.pageSize) || 0
const clusters = await AdminClusterService.getPage(skip, pagination.pageSize)
if (!clusters?.items)
throw Error(`Не удалось загрузить список кустов`)
setClusters(clusters.items)
setPagintaionTotal(clusters.count ?? clusters.items.length ?? 0)
},
setShowLoader,
`Не удалось загрузить список кустов`
)
useEffect(() => invokeWebApiWrapperAsync(
async () => {
let deposits = await DepositService.getDeposits()
deposits = deposits?.map((deposit) => ({ key: deposit.id, label: deposit.caption }))
setDeposits(deposits ?? [])
},
setShowLoader,
`Не удалось загрузить список месторождений`
), [])
useEffect(updateTable, [pagination, history])
const onAdd = async (cluster) => {
await AdminClusterService.insert(cluster)
updateTable()
}
const onEdit = async (cluster) => {
if (!cluster.id) return
await AdminClusterService.put(cluster.id, cluster)
updateTable()
}
const onDelete = async (cluster) => {
if (!cluster.id) return
await AdminClusterService.delete(cluster.id)
updateTable()
}
return (
<LoaderPortal show={showLoader}>
<EditableTable
size={'small'}
bordered
columns={clusterColumns}
dataSource={clusters}
onRowAdd={onAdd}
onRowEdit={onEdit}
onRowDelete={onDelete}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagintaionTotal,
showSizeChanger: false,
onChange: (current, pageSize) => setPagination({ current, pageSize })
}}
/>
</LoaderPortal>
)
}

View File

@ -0,0 +1,75 @@
import { useEffect, useState } from 'react'
import { useHistory, useParams } from 'react-router'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { EditableTable, makeColumn } from '../../components/Table'
import { AdminCompanyService } from '../../services/api'
const companyColumns = [
makeColumn('Название', 'caption', { width: 200, editable: true }),
makeColumn('Тип компании', 'companyTypeCaption', { width: 200, editable: true })
]
export const CompanyController = () => {
const { page } = useParams()
const history = useHistory()
const [companies, setCompanies] = useState([])
const [pagination, setPagination] = useState({current: page ?? 1, pageSize: 20})
const [pagintaionTotal, setPagintaionTotal] = useState(0)
const [showLoader, setShowLoader] = useState(false)
const updateTable = () => invokeWebApiWrapperAsync(
async() => {
history.push(`/admin/company/${pagination.current}`)
const skip = ((pagination.current - 1) * pagination.pageSize) || 0
const companies = await AdminCompanyService.getPage(skip, pagination.pageSize)
if (!companies?.items)
throw Error(`Не удалось загрузить список кустов`)
setCompanies(companies.items)
setPagintaionTotal(companies.count ?? companies.items.length ?? 0)
},
setShowLoader,
`Не удалось загрузить список кустов`
)
useEffect(updateTable, [pagination, history])
const onAdd = async (company) => {
await AdminCompanyService.insert(company)
updateTable()
}
const onEdit= async (company) => {
if(!company.id) return
await AdminCompanyService.put(company.id, company)
updateTable()
}
const onDelete= async (company) => {
if(!company.id) return
await AdminCompanyService.delete(company.id)
updateTable()
}
return (
<LoaderPortal show={showLoader}>
<EditableTable
size={'small'}
bordered
columns={companyColumns}
dataSource={companies}
onRowAdd={onAdd}
onRowEdit={onEdit}
onRowDelete={onDelete}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagintaionTotal,
showSizeChanger: false,
onChange: (current, pageSize) => setPagination({ current, pageSize })
}}
/>
</LoaderPortal>
)
}

View File

@ -0,0 +1,77 @@
import { useEffect, useState } from 'react'
import { useHistory, useParams } from 'react-router'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { EditableTable, makeColumn, makeNumericColumn } from '../../components/Table'
import { AdminDepositService } from '../../services/api'
const depositColumns = [
makeColumn('Название', 'caption', { width: 200, editable: true }),
makeColumn('Описание', 'description', { width: 500, editable: true }),
makeNumericColumn('Широта', 'latitude', null, null, null, 150, { editable: true }),
makeNumericColumn('Долгота', 'longitude', null, null, null, 150, { editable: true })
]
export const DepositController = () => {
const { page } = useParams()
const history = useHistory()
const [deposits, setDeposits] = useState([])
const [pagination, setPagination] = useState({current: page ?? 1, pageSize: 20})
const [pagintaionTotal, setPagintaionTotal] = useState(0)
const [showLoader, setShowLoader] = useState(false)
const updateTable = () => invokeWebApiWrapperAsync(
async() => {
history.push(`/admin/deposit/${pagination.current}`)
const skip = ((pagination.current - 1) * pagination.pageSize) || 0
const deposits = await AdminDepositService.getPage(skip, pagination.pageSize)
if (!deposits?.items)
throw Error(`Не удалось загрузить список месторождении`)
setDeposits(deposits.items)
setPagintaionTotal(deposits.count ?? deposits.items.length ?? 0)
},
setShowLoader,
`Не удалось загрузить список месторождении`
)
useEffect(updateTable, [pagination, history])
const onAdd = async (deposit) => {
await AdminDepositService.insert(deposit)
updateTable()
}
const onEdit= async (deposit) => {
if(!deposit.id) return
await AdminDepositService.put(deposit.id, deposit)
updateTable()
}
const onDelete= async (deposit) => {
if(!deposit.id) return
await AdminDepositService.delete(deposit.id)
updateTable()
}
return (
<LoaderPortal show={showLoader}>
<EditableTable
size={'small'}
bordered
columns={depositColumns}
dataSource={deposits}
onRowAdd={onAdd}
onRowEdit={onEdit}
onRowDelete={onDelete}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagintaionTotal,
showSizeChanger: false,
onChange: (current, pageSize) => setPagination({ current, pageSize })
}}
/>
</LoaderPortal>
)
}

View File

@ -0,0 +1,104 @@
import { useEffect, useState } from 'react'
import { useHistory, useParams } from 'react-router'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { EditableTable, makeColumn, makeSelectColumn } from '../../components/Table'
import { AdminCompanyService, AdminUserService } from '../../services/api'
import { loginRules, nameRules, phoneRules, emailRules } from '../../utils/validationRules'
const maxCompaniesCount = 1500
export const UserController = () => {
const { page } = useParams()
const history = useHistory()
const [companies, setCompanies] = useState([])
const [users, setUsers] = useState([])
const [pagination, setPagination] = useState({current: page ?? 1, pageSize: 20})
const [pagintaionTotal, setPagintaionTotal] = useState(0)
const [showLoader, setShowLoader] = useState(false)
const userColumns = [
makeColumn('Логин', 'login', {
editable: true,
formItemRules: [{ required: true }, ...loginRules]
}),
makeColumn('Фамилия', 'surname', {
editable: true,
formItemRules: [{ required: true }, ...nameRules]
}),
makeColumn('Имя', 'name', { editable: true, formItemRules: nameRules }),
makeColumn('Отчество', 'patronymic', { editable: true, formItemRules: nameRules }),
makeColumn('E-mail', 'email', {
editable: true,
formItemRules: [{ required: true }, ...emailRules]
}),
makeColumn('Номер телефона', 'phone', { editable: true, formItemRules: phoneRules }),
makeColumn('Должность', 'position', { editable: true }),
makeSelectColumn('Компания', 'idCompany', companies, '--', { editable: true })
]
const updateTable = () => invokeWebApiWrapperAsync(
async() => {
history.push(`/admin/user/${pagination.current}`)
const skip = ((pagination.current - 1) * pagination.pageSize) || 0
const users = await AdminUserService.getPage(skip, pagination.pageSize)
if (!users?.items)
throw Error(`Не удалось загрузить список пользователей`)
setUsers(users.items)
setPagintaionTotal(users.count ?? users.items.length ?? 0)
},
setShowLoader,
`Не удалось загрузить список пользователей`
)
useEffect(() => invokeWebApiWrapperAsync(
async () => {
let companies = await AdminCompanyService.getPage(0, maxCompaniesCount)
companies = companies?.map((company) => ({ key: company.id, label: company.caption }))
setCompanies(companies?.items ?? [])
},
setShowLoader,
`Не удалось загрузить список компаний`
), [])
useEffect(updateTable, [pagination, history])
const onAdd = async (user) => {
await AdminUserService.insert(user)
updateTable()
}
const onEdit = async (user) => {
if(!user.id) return
await AdminUserService.put(user.id, user)
updateTable()
}
const onDelete = async (user) => {
if(!user.id) return
await AdminUserService.delete(user.id)
updateTable()
}
return (
<LoaderPortal show={showLoader}>
<EditableTable
size={'small'}
bordered
columns={userColumns}
dataSource={users}
onRowAdd={onAdd}
onRowEdit={onEdit}
onRowDelete={onDelete}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagintaionTotal,
showSizeChanger: false,
onChange: (current, pageSize) => setPagination({ current, pageSize })
}}
/>
</LoaderPortal>
)
}

View File

@ -0,0 +1,89 @@
import { useEffect, useState } from 'react'
import { useHistory, useParams } from 'react-router'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import LoaderPortal from '../../components/LoaderPortal'
import { EditableTable, makeColumn, makeNumericColumn, makeSelectColumn } from '../../components/Table'
import { AdminWellService, ClusterService } from '../../services/api'
export const WellController = () => {
const { page } = useParams()
const history = useHistory()
const [clusters, setClusters] = useState([])
const [wells, setWells] = useState([])
const [pagination, setPagination] = useState({ current: page ?? 1, pageSize: 20 })
const [pagintaionTotal, setPagintaionTotal] = useState(0)
const [showLoader, setShowLoader] = useState(false)
const wellColumns = [
makeSelectColumn('Куст', 'idCluster', clusters, '--', { width: 200 , editable: true }),
makeColumn('Название', 'caption', { width: 200, editable: true }),
makeColumn('Описание', 'description', { width: 500, editable: true }),
makeNumericColumn('Широта', 'latitude', null, null, null, 150, { editable: true }),
makeNumericColumn('Долгота', 'longitude', null, null, null, 150, { editable: true })
]
const updateTable = () => invokeWebApiWrapperAsync(
async () => {
history.push(`/admin/well/${pagination.current}`)
const skip = ((pagination.current - 1) * pagination.pageSize) || 0
const wells = await AdminWellService.getPage(skip, pagination.pageSize)
if (!wells?.items)
throw Error(`Не удалось загрузить список скважин`)
setWells(wells.items)
setPagintaionTotal(wells.count ?? wells.items.length ?? 0)
},
setShowLoader,
`Не удалось загрузить список скважин`
)
useEffect(() => invokeWebApiWrapperAsync(
async () => {
let clusters = await ClusterService.getClusters()
clusters = clusters?.map((cluster) => ({ key: cluster.id, label: cluster.caption }))
setClusters(clusters ?? [])
},
setShowLoader,
`Не удалось загрузить список кустов`
), [])
useEffect(updateTable, [pagination, history])
const onAdd = async (well) => {
await AdminWellService.insert(well)
updateTable()
}
const onEdit = async (well) => {
if (!well.id) return
await AdminWellService.put(well.id, well)
updateTable()
}
const onDelete = async (well) => {
if (!well.id) return
await AdminWellService.delete(well.id)
updateTable()
}
return (
<LoaderPortal show={showLoader}>
<EditableTable
size={'small'}
bordered
columns={wellColumns}
dataSource={wells}
onRowAdd={onAdd}
onRowEdit={onEdit}
onRowDelete={onDelete}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagintaionTotal,
showSizeChanger: false,
onChange: (current, pageSize) => setPagination({ current, pageSize })
}}
/>
</LoaderPortal>
)
}

View File

@ -0,0 +1,66 @@
import { Layout, Menu } from 'antd'
import { Switch, Link, useParams, Redirect, Route } from 'react-router-dom'
import { PrivateMenuItem, PrivateRoute } from '../../components/Private'
import { ClusterController } from './ClusterController'
import { CompanyController } from './CompanyController'
import { DepositController } from './DepositController'
import { UserController } from './UserController'
import { WellController } from './WellController'
export const AdminPanel = () => {
const { tab } = useParams()
const rootPath = '/admin'
return (
<Layout>
<Menu
mode={'horizontal'}
selectable={true}
selectedKeys={[tab]}
>
<PrivateMenuItem roles={['deposit_admin']} key={'deposit'}>
<Link to={`${rootPath}/deposit`}>Управление месторождениями</Link>
</PrivateMenuItem>
<PrivateMenuItem roles={['cluster_admin']} key={'cluster'}>
<Link to={`${rootPath}/cluster`}>Управление кустами</Link>
</PrivateMenuItem>
<PrivateMenuItem roles={['well_admin']} key={'well'}>
<Link to={`${rootPath}/well`}>Управление скважинами</Link>
</PrivateMenuItem>
<PrivateMenuItem roles={['user_admin']} key={'user'}>
<Link to={`${rootPath}/user`}>Управление пользователями</Link>
</PrivateMenuItem>
<PrivateMenuItem roles={['company_admin']} key={'company'}>
<Link to={`${rootPath}/company`}>Управление компаниями</Link>
</PrivateMenuItem>
</Menu>
<Layout>
<Layout.Content className={'site-layout-background'}>
<Switch>
<PrivateRoute roles={['deposit_admin']} path={`${rootPath}/deposit/:page?`}>
<DepositController />
</PrivateRoute>
<PrivateRoute roles={['cluster_admin']} path={`${rootPath}/cluster/:page?`}>
<ClusterController />
</PrivateRoute>
<PrivateRoute roles={['well_admin']} path={`${rootPath}/well/:page?`}>
<WellController />
</PrivateRoute>
<PrivateRoute roles={['user_admin']} path={`${rootPath}/user/:page?`}>
<UserController />
</PrivateRoute>
<PrivateRoute roles={['company_admin']} path={`${rootPath}/company/:page?`}>
<CompanyController />
</PrivateRoute>
<Route path={'/'}>
<Redirect to={`${rootPath}/deposit`}/>
</Route>
</Switch>
</Layout.Content>
</Layout>
</Layout>
)
}
export default AdminPanel

View File

@ -1,30 +1,37 @@
import { Redirect, Route, Switch } from "react-router-dom"; import { Redirect, Route, Switch } from 'react-router-dom'
import Deposit from "./Deposit"; import { PrivateRoute } from '../components/Private'
import Cluster from "./Cluster"; import { AdminLayoutPortal, LayoutPortal } from '../components/Layout'
import Well from "./Well"; import Deposit from './Deposit'
import {LayoutPortal} from "../components/LayoutPortal"; import Cluster from './Cluster'
import Well from './Well'
import AdminPanel from './AdminPanel'
export default function Main() { export const Main = () => (
return (
<Switch> <Switch>
<Route path="/deposit"> <PrivateRoute path={'/admin/:tab?'} roles={['admin']}>
<AdminLayoutPortal title={'Администраторская панель'}>
<AdminPanel />
</AdminLayoutPortal>
</PrivateRoute>
<Route path={'/deposit'}>
<LayoutPortal noSheet title='Месторождение'> <LayoutPortal noSheet title='Месторождение'>
<Deposit /> <Deposit />
</LayoutPortal> </LayoutPortal>
</Route> </Route>
<Route path="/cluster/:idClaster/:tab?"> <Route path={'/cluster/:idClaster/:tab?'}>
<LayoutPortal title="Анализ скважин куста"> <LayoutPortal title={'Анализ скважин куста'}>
<Cluster /> <Cluster />
</LayoutPortal> </LayoutPortal>
</Route> </Route>
<Route path="/well/:idWell/:tab?"> <Route path={'/well/:idWell/:tab?'}>
<LayoutPortal> <LayoutPortal>
<Well /> <Well />
</LayoutPortal> </LayoutPortal>
</Route> </Route>
<Route path="/"> <Route path={'/'}>
<Redirect to={{ pathname: `/deposit` }} /> <Redirect to={{ pathname: `/deposit` }} />
</Route> </Route>
</Switch> </Switch>
); )
}
export default Main