forked from ddrilling/asb_cloud_front
На странице мониторинга добавлена "Рекомендация установок"
This commit is contained in:
parent
43a01bd57f
commit
f60451eb4b
@ -1,33 +0,0 @@
|
||||
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 WellTreeSelector from './WellTreeSelector'
|
||||
|
||||
const { Header } = Layout
|
||||
|
||||
export default function PageHeader({title='Мониторинг', wellsList}){
|
||||
const login = localStorage['login']
|
||||
|
||||
let handleLogout = () => {
|
||||
localStorage.removeItem('login')
|
||||
localStorage.removeItem('token')
|
||||
}
|
||||
|
||||
return(
|
||||
<Layout>
|
||||
<Header className="header">
|
||||
<Link to="/" >
|
||||
<img src={logo} alt="АСБ" className="logo"/>
|
||||
</Link>
|
||||
<WellTreeSelector wellsList={wellsList}/>
|
||||
<h1 className="title">{title}</h1>
|
||||
<Link to="/login" onClick={handleLogout}>
|
||||
<Button icon={<UserOutlined/>}>
|
||||
({login}) Выход
|
||||
</Button>
|
||||
</Link>
|
||||
</Header>
|
||||
</Layout>
|
||||
)
|
||||
};
|
36
src/components/PageHeader.tsx
Normal file
36
src/components/PageHeader.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
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 WellTreeSelector from './WellTreeSelector'
|
||||
import { headerHeight } from '../utils'
|
||||
|
||||
const { Header } = Layout
|
||||
|
||||
const logoStyle = { height: headerHeight }
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('login')
|
||||
localStorage.removeItem('token')
|
||||
}
|
||||
|
||||
type PageHeaderProps = { title?: string }
|
||||
|
||||
export const PageHeader = ({ title = 'Мониторинг' }: PageHeaderProps) => (
|
||||
<Layout>
|
||||
<Header className={'header'}>
|
||||
<Link to={'/'} style={logoStyle}>
|
||||
<img src={logo} alt={'АСБ'} className={'logo'}/>
|
||||
</Link>
|
||||
<WellTreeSelector />
|
||||
<h1 className={'title'}>{title}</h1>
|
||||
<Link to={'/login'} onClick={handleLogout}>
|
||||
<Button icon={<UserOutlined/>}>
|
||||
({localStorage['login']}) Выход
|
||||
</Button>
|
||||
</Link>
|
||||
</Header>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
export default PageHeader
|
@ -19,13 +19,11 @@ type PeriodPickerProps = {
|
||||
}
|
||||
|
||||
export const PeriodPicker = ({ defaultValue = defaultPeriod, onChange }: PeriodPickerProps) => (
|
||||
<Select defaultValue={defaultValue} onChange={(value) => onChange?.(Number(value))}>
|
||||
{timePeriodCollection.map(period => (
|
||||
<Select.Option key={period.value} value={period.value}>
|
||||
{period.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
defaultValue={defaultValue}
|
||||
onChange={(value) => onChange?.(Number(value))}
|
||||
options={timePeriodCollection}
|
||||
/>
|
||||
)
|
||||
|
||||
export default PeriodPicker
|
||||
|
@ -1,38 +0,0 @@
|
||||
import { Form, Input} from "antd"
|
||||
|
||||
export const EditableCell = ({
|
||||
editing,
|
||||
record,
|
||||
dataIndex,
|
||||
input,
|
||||
isRequired,
|
||||
title,
|
||||
formItemClass,
|
||||
formItemRules,
|
||||
children,
|
||||
initialValue,
|
||||
}) => {
|
||||
|
||||
const inputNode = input ?? <Input/>
|
||||
const rules = formItemRules ?? [{
|
||||
required: isRequired,
|
||||
message: `Please Input ${title}!`,
|
||||
}]
|
||||
|
||||
const editor = <Form.Item
|
||||
name={dataIndex}
|
||||
style={{margin:0}}
|
||||
className={formItemClass}
|
||||
rules={rules}
|
||||
initialValue={initialValue}>
|
||||
{inputNode}
|
||||
</Form.Item>
|
||||
|
||||
const tdStyle = editing
|
||||
? { padding:0 }
|
||||
: null
|
||||
|
||||
return (<td style={tdStyle}>
|
||||
{editing ? editor: children}
|
||||
</td>)
|
||||
}
|
43
src/components/Table/EditableCell.tsx
Normal file
43
src/components/Table/EditableCell.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { Form, Input } from 'antd'
|
||||
import { NamePath, Rule } from 'rc-field-form/lib/interface'
|
||||
|
||||
type EditableCellProps = {
|
||||
editing?: boolean
|
||||
dataIndex?: NamePath
|
||||
input?: React.Component
|
||||
isRequired?: boolean
|
||||
title: string
|
||||
formItemClass?: string
|
||||
formItemRules?: Rule[]
|
||||
children: React.ReactNode
|
||||
initialValue: any
|
||||
}
|
||||
|
||||
export const EditableCell = ({
|
||||
editing,
|
||||
dataIndex,
|
||||
input,
|
||||
isRequired,
|
||||
title,
|
||||
formItemClass,
|
||||
formItemRules,
|
||||
children,
|
||||
initialValue,
|
||||
}: EditableCellProps) => (
|
||||
<td style={editing ? { padding: 0 } : undefined}>
|
||||
{!editing ? children : (
|
||||
<Form.Item
|
||||
name={dataIndex}
|
||||
style={{ margin: 0 }}
|
||||
className={formItemClass}
|
||||
rules={formItemRules ?? [{
|
||||
required: isRequired,
|
||||
message: `Please Input ${title}!`,
|
||||
}]}
|
||||
initialValue={initialValue}
|
||||
>
|
||||
{input ?? <Input/>}
|
||||
</Form.Item>
|
||||
)}
|
||||
</td>
|
||||
)
|
@ -20,7 +20,8 @@ export const EditableTable = ({
|
||||
onRowAdd, // Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается
|
||||
onRowEdit,// Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается
|
||||
onRowDelete,// Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается
|
||||
...otherTableProps}) => {
|
||||
...otherTableProps
|
||||
}) => {
|
||||
|
||||
const [form] = Form.useForm()
|
||||
const [data, setData] = useState(tryAddKeys(dataSource))
|
||||
|
@ -10,21 +10,19 @@ export { SelectFromDictionary } from './SelectFromDictionary'
|
||||
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
|
||||
export const formatDate='YYYY.MM.DD HH:mm'
|
||||
|
||||
export const makeNumericRender = (fixed?:number) => (value: any, row: object): ReactNode => {
|
||||
const placeholder = '-'
|
||||
let val = placeholder
|
||||
if((value !== null) &&
|
||||
(value !== undefined) &&
|
||||
!Number.isNaN(value) &&
|
||||
Number.isFinite(value)){
|
||||
export const makeNumericRender = (fixed?: number) => (value: any, row: object): ReactNode => {
|
||||
let val = '-'
|
||||
if (value && Number.isFinite(+value)) {
|
||||
val = !!fixed
|
||||
? (+value).toFixed(fixed)
|
||||
: (+value).toPrecision(5)
|
||||
}
|
||||
|
||||
return (<div className='text-align-r-container'>
|
||||
return (
|
||||
<div className={'text-align-r-container'}>
|
||||
<span>{val}</span>
|
||||
</div>)
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const makeNumericColumnOptions = (fixed?:number, sorterKey?:string ) => ({
|
||||
|
38
src/images/logoSmaill.svg
Normal file
38
src/images/logoSmaill.svg
Normal file
@ -0,0 +1,38 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1018.0595 0 8006.9226 3574.0189" version="1.1" fill="#2d2242">
|
||||
<clipPath id="c">
|
||||
<path d="m 5189,716 c 587,0 1063,476 1063,1063 0,587 -476,1063 -1063,1063 -588,0 -1064,-476 -1064,-1063 0,-587 476,-1063 1064,-1063 z" />
|
||||
</clipPath>
|
||||
<mask id="m" fill="#000">
|
||||
<rect width="100%" height="100%" fill="#fff"/>
|
||||
<polygon points="5166,1737 5061,1830 5089,1676"/>
|
||||
<polygon points="5288,1737 5393,1830 5365,1676"/>
|
||||
<polygon points="5224,1696 5285,1654 5172,1654"/>
|
||||
<polygon points="5143,2007 5019,2062 5039,1952"/>
|
||||
<polygon points="5310,2007 5435,2062 5415,1952"/>
|
||||
<polygon points="5091,1894 5229,1962 5365,1894 5229,1783"/>
|
||||
<polygon points="5052,2132 5232,2251 5412,2130 5229,2043"/>
|
||||
<polygon points="5163,2297 4949,2445 4996,2184"/>
|
||||
<polygon points="5292,2297 5505,2445 5458,2184"/>
|
||||
<polygon points="5226,2337 5497,2523 4958,2523"/>
|
||||
</mask>
|
||||
<g fill="#e31e21">
|
||||
<path d="M 1756,3564 H 1018 L 3236,2 3848,3 4452,0 4400,184 C 4637,66 4905,0 5189,0 5751,0 6253,261 6579,669 l -233,810 C 6213,964 5745,584 5189,584 c -528,0 -975,341 -1134,815 l -30,108 c -20,87 -31,178 -31,272 0,660 535,1195 1195,1195 318,0 607,-125 821,-327 l -220,764 c -185,88 -388,147 -601,147 -636,0 -1194,-334 -1508,-836 l -239,842 h -702 l 187,-595 H 2146 Z M 3082,2443 3703,446 2463,2444 Z"/>
|
||||
<path d="m 7725,3574 c -392,-2 -748,-14 -1152,-2 L 5869,3559 6882,2 l 1790,1 -136,559 -1176,9 -121,462 h 836 c 570,93 953,697 950,1254 -3,656 -585,1291 -1300,1287 z m -995,-606 c 333,0 665,0 998,2 381,2 691,-335 693,-686 1,-291 -206,-632 -510,-673 h -824 z"/>
|
||||
</g>
|
||||
<g mask="url(#m)">
|
||||
<polygon points="5347,1437 5105,1437 5105,1315 5347,1315"/>
|
||||
<polygon points="5455,1555 4992,1555 4992,1469 5455,1469"/>
|
||||
<polygon points="5597,2523 4860,2523 5027,1587 5419,1587"/>
|
||||
</g>
|
||||
<polygon points="5246,2523 5200,2523 5200,1735 5246,1735"/>
|
||||
<g clip-path="url(#c)">
|
||||
<g fill="#9d9e9e">
|
||||
<path d="m 5136,177 c -688,66 -1152,378 -1415,911 l 1475,-196 783,591 z"/>
|
||||
<path d="M 6684,1229 C 6401,599 5957,260 5367,182 l 659,1333 -308,931 z"/>
|
||||
</g>
|
||||
<path d="m 6189,3044 c 509,-466 692,-994 581,-1579 l -1059,1044 -981,-1 z"/>
|
||||
<path d="m 4267,3105 c 598,345 1157,360 1681,78 L 4633,2488 4337,1552 Z"/>
|
||||
<path d="m 3626,1346 c -142,676 17,1212 447,1622 l 253,-1466 798,-571 z"/>
|
||||
</g>
|
||||
<path fill="none" d="m 5189,716 c 587,0 1063,476 1063,1063 0,587 -476,1063 -1063,1063 -588,0 -1064,-476 -1064,-1063 0,-587 476,-1063 1064,-1063 z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import moment from 'moment'
|
||||
import { DatePicker } from 'antd'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
127
src/pages/TelemetryView/Setpoints.jsx
Normal file
127
src/pages/TelemetryView/Setpoints.jsx
Normal file
@ -0,0 +1,127 @@
|
||||
import { Button, Input, Modal, Select } from 'antd'
|
||||
import { useState } from 'react'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import PeriodPicker, { defaultPeriod } from '../../components/PeriodPicker'
|
||||
import { EditableTable, makeNumericRender } from '../../components/Table'
|
||||
import { SetpointsService } from '../../services/api'
|
||||
|
||||
export const Setpoints = ({ idWell, ...other }) => {
|
||||
const [isModalShown, setIsModalShown] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isUploading, setIsUploading] = useState(false)
|
||||
const [setpointNames, setSetpointNames] = useState([])
|
||||
const [setpoints, setSetpoints] = useState([])
|
||||
const [comment, setComment] = useState('')
|
||||
const [expirePeriod, setExpirePeriod] = useState(defaultPeriod)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Наименование установки',
|
||||
dataIndex: 'name',
|
||||
editable: true,
|
||||
isRequired: true,
|
||||
width: 200,
|
||||
input: <Select options={setpointNames} />,
|
||||
render: (val) => setpointNames.find((name) => name.value === val)?.label
|
||||
}, {
|
||||
title: 'Значение',
|
||||
dataIndex: 'value',
|
||||
editable: true,
|
||||
isRequired: true,
|
||||
width: 125,
|
||||
render: makeNumericRender(7),
|
||||
align: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
const onOpenClick = () => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const names = await SetpointsService.getSetpointsNamesByIdWell(idWell)
|
||||
if (!names) throw Error('Setpoints not found')
|
||||
setSetpointNames(names.map(spn => ({
|
||||
label: spn.displayName,
|
||||
value: spn.name,
|
||||
tooltip: spn.comment
|
||||
})))
|
||||
setIsModalShown(true)
|
||||
},
|
||||
setIsLoading,
|
||||
`Не удалось загрузить список для скважины "${idWell}"`
|
||||
)
|
||||
|
||||
const onModalOk = () => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
// eslint-disable-next-line no-sequences
|
||||
const setpointsObject = setpoints.reduce((obj, sp) => (obj[sp.name] = sp.value, obj), {})
|
||||
const request = {
|
||||
uploadDate: new Date(),
|
||||
obsolescenceSec: expirePeriod,
|
||||
setpoints: setpointsObject,
|
||||
comment: comment
|
||||
}
|
||||
await SetpointsService.insert(idWell, request)
|
||||
setIsModalShown(false)
|
||||
},
|
||||
setIsUploading,
|
||||
`Не удалось отправить рекомендации по скважине "${idWell}"`
|
||||
)
|
||||
|
||||
const onAdd = async (setpoint) => setSetpoints((prevSetpoints) => {
|
||||
setpoint.key = Date.now()
|
||||
prevSetpoints.push(setpoint)
|
||||
return prevSetpoints
|
||||
})
|
||||
|
||||
const onEdit = async (setpoint) => setSetpoints((prevSetpoints) => {
|
||||
const idx = prevSetpoints.findIndex((val) => val.key === setpoint.key)
|
||||
prevSetpoints[idx] = setpoint
|
||||
return prevSetpoints
|
||||
})
|
||||
|
||||
const onDelete = async (setpoint) => setSetpoints((prevSetpoints) => {
|
||||
const idx = prevSetpoints.findIndex((val) => val.key === setpoint.key)
|
||||
prevSetpoints.splice(idx, 1)
|
||||
return prevSetpoints
|
||||
})
|
||||
|
||||
return (
|
||||
<div {...other}>
|
||||
<Button onClick={onOpenClick} loading={isLoading}>
|
||||
Рекомендовать установки
|
||||
</Button>
|
||||
<Modal
|
||||
width={800}
|
||||
title={'Рекомендация установок'}
|
||||
visible={isModalShown}
|
||||
onCancel={() => setIsModalShown(false)}
|
||||
onOk={onModalOk}
|
||||
okText={'Отправить'}
|
||||
>
|
||||
<LoaderPortal show={isUploading}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
Период актуальности рекомендаций:
|
||||
<PeriodPicker onChange={setExpirePeriod} />
|
||||
</div>
|
||||
<EditableTable
|
||||
bordered
|
||||
columns={columns}
|
||||
dataSource={setpoints}
|
||||
onRowAdd={onAdd}
|
||||
onRowEdit={onEdit}
|
||||
onRowDelete={onDelete}
|
||||
pagination={false}
|
||||
style={{ margin: '10px 0' }}
|
||||
/>
|
||||
Комментарий:
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
onChange={(e) => setComment(e.value)}
|
||||
value={comment}
|
||||
required
|
||||
/>
|
||||
</LoaderPortal>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -6,6 +6,7 @@ import { CustomColumn } from './CustomColumn'
|
||||
import ActiveMessagesOnline from './ActiveMessagesOnline'
|
||||
import { ModeDisplay } from './ModeDisplay'
|
||||
import { UserOfWell } from './UserOfWells'
|
||||
import { Setpoints } from './Setpoints'
|
||||
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { Grid, GridItem, Flex } from '../../components/Grid'
|
||||
@ -380,6 +381,7 @@ export default function TelemetryView({ idWell }) {
|
||||
<Option value={2}>Завершено</Option>
|
||||
</Select>
|
||||
</div>
|
||||
<Setpoints idWell={idWell} style={{ marginLeft: '1rem' }} />
|
||||
<span style={{ flexGrow: 20 }}> </span>
|
||||
<img src={isTorqueStabEnabled(dataSpin) ? MomentStabPicEnabled : MomentStabPicDisabled} style={{ marginRight: '15px' }} alt={'TorqueMaster'} />
|
||||
<img src={isSpinEnabled(dataSpin) ? SpinPicEnabled : SpinPicDisabled} style={{ marginRight: '15px' }} alt={'SpinMaster'} />
|
||||
|
9
src/utils/index.ts
Normal file
9
src/utils/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export type { RawDate } from './DateTimeUtils'
|
||||
export { isRawDate } from './DateTimeUtils'
|
||||
|
||||
export const headerHeight = 64
|
||||
|
||||
export const mainFrameSize = () => ({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight - headerHeight
|
||||
})
|
Loading…
Reference in New Issue
Block a user