Merge branch 'dev' into feature/CF2-72-AdminPanel

This commit is contained in:
Александр Сироткин 2021-12-27 15:31:37 +05:00
commit 8552a40506
25 changed files with 670 additions and 703 deletions

View File

@ -1,6 +1,6 @@
import { DatePicker } from 'antd'
import moment from 'moment'
import { formatDate } from './index'
import { defaultFormat } from '../../utils'
export type DatePickerWrapperProps = {
value: moment.Moment,
@ -13,7 +13,7 @@ export const DatePickerWrapper: React.FC<DatePickerWrapperProps> = ({value, onCh
allowClear={false}
defaultValue={moment()}
value={moment.utc(value).local()}
format={formatDate}
format={defaultFormat}
showTime
onChange={(date) => onChange(date)}
{...other}

View File

@ -6,17 +6,25 @@ import { invokeWebApiWrapperAsync } from '../factory'
const newRowKeyValue = 'newRow'
export const makeActionHandler = (action, { service, setLoader, errorMsg, onComplete }) => service && action && (
export const makeActionHandler = (action, { idWell, service, setLoader, errorMsg, onComplete }, recordParser) => service && action && (
(record) => invokeWebApiWrapperAsync(
async () => {
const addIdWell = (...params) => idWell ? [idWell, ...params] : params
if (typeof recordParser === 'function')
record = recordParser(record)
if (action === 'insert') {
record.key = Date.now()
await service.insert(record)
await service.insert(...addIdWell(record))
} else if (action === 'insertRange') {
if (!Array.isArray(record))
record = [record]
await service.insertRange(...addIdWell(record))
} else if (record.id) {
if (action === 'update')
await service.put(record.id, record)
await service.put(...addIdWell(record.id, record))
else if (action === 'delete')
await service.delete(record.id)
await service.delete(...addIdWell(record.id))
}
if (onComplete)
await onComplete()

View File

@ -10,7 +10,6 @@ export { DatePickerWrapper } from './DatePickerWrapper'
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 => {
let val = '-'
@ -209,7 +208,7 @@ export const makeSelectColumn = <T extends unknown = string>(
other?: columnPropsOther
) => makeColumn(title, dataIndex, {
input: <Select options={options}/>,
render: (value) => options.find(option => option?.value === value)?.label ?? defaultValue ?? '--',
render: (value) => options?.find(option => option?.value === value)?.label ?? defaultValue ?? value ?? '--',
...other
})

View File

@ -2,14 +2,17 @@ import { TreeSelect } from 'antd'
import { DefaultValueType } from 'rc-tree-select/lib/interface'
import { useState, useEffect, ReactNode } from 'react'
import { useHistory, useRouteMatch } from 'react-router-dom'
import LoaderPortal from './LoaderPortal'
import { DepositService } from '../services/api'
import { invokeWebApiWrapperAsync } from './factory'
import { WellIcon, WellIconState } from './icons'
import { ReactComponent as DepositIcon } from '../images/DepositIcon.svg'
import { ReactComponent as ClusterIcon } from '../images/ClusterIcon.svg'
import { DepositDto } from '../services/api'
import { RawDate } from '../utils/DateTimeUtils'
import { RawDate } from '../utils'
import LoaderPortal from './LoaderPortal'
import { invokeWebApiWrapperAsync } from './factory'
import { WellIcon, WellIconState } from './icons'
import '../styles/wellTreeSelect.css'
export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown'

View File

@ -1,12 +1,12 @@
import { Dispatch, SetStateAction } from "react"
import { notification } from 'antd';
import { notification } from 'antd'
import { Dispatch, SetStateAction } from 'react'
import { FileInfoDto } from '../services/api'
const notificationTypeDictionary = new Map([
['error', {notifyInstance: notification.error, caption: 'Ошибка'}],
['warning', {notifyInstance: notification.warning, caption: 'Предупреждение'}],
['info', {notifyInstance: notification.info, caption: 'Инфо'}],
['open', {notifyInstance: notification.info, caption: ''}],
['error' , { notifyInstance: notification.error , caption: 'Ошибка' }],
['warning', { notifyInstance: notification.warning, caption: 'Предупреждение' }],
['info' , { notifyInstance: notification.info , caption: 'Инфо' }],
['open' , { notifyInstance: notification.info , caption: '' }],
])
/**
@ -14,30 +14,29 @@ const notificationTypeDictionary = new Map([
* @param body string или ReactNode
* @param notifyType для параметра типа. Допустимые значение 'error', 'warning', 'info'
*/
export const notify = (body: string|any, notifyType:string ='info', other?: any) => {
if(!body)
return
const instance = notificationTypeDictionary.get(notifyType) ??
export const notify = (body: string | any, notifyType: string = 'info', other?: any) => {
if (!body) return
const instance = notificationTypeDictionary.get(notifyType) ??
notificationTypeDictionary.get('open')
instance?.notifyInstance({
description: body,
message: instance.caption,
placement: "bottomRight",
placement: 'bottomRight',
duration: 10,
...other
})
}
type asyncFunction = (...args:any) => Promise<any|void>;
type asyncFunction = (...args: any) => Promise<any|void>
export const invokeWebApiWrapperAsync = async (
funcAsync: asyncFunction,
setShowLoader: Dispatch<SetStateAction<boolean>>,
errorNotifyText: (string | ((ex: unknown) => string))
setShowLoader?: Dispatch<SetStateAction<boolean>>,
errorNotifyText?: (string | ((ex: unknown) => string))
) => {
if(setShowLoader)
setShowLoader(true)
setShowLoader?.(true)
try{
await funcAsync()
} catch (ex) {
@ -48,49 +47,43 @@ export const invokeWebApiWrapperAsync = async (
notify(errorNotifyText(ex), 'error')
else notify(errorNotifyText, 'error')
}
} finally{
if(setShowLoader)
setShowLoader(false)
} finally {
setShowLoader?.(false)
}
}
export const download = async (url:string, fileName?:string) => {
export const download = async (url: string, fileName?: string) => {
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ' + localStorage['token']
Authorization: `Bearer ${localStorage['token']}`
},
method: 'Get'
})
if(response.status !== 200)
throw response
const requestFileName = decodeURI(fileName
??response.headers
.get('content-disposition')
?.split(';')
.splice(-1)[0]
.split("'")
.splice(-1)[0]
?? url.replace('\\','/')
.split('/')
.splice(-1)[0]
?? 'file')
const requestFileName = decodeURI(
fileName
?? response.headers.get('content-disposition')?.split(';').pop()?.split(`'`).pop()
?? url.replace('\\','/').split('/').pop()
?? 'file'
)
const blob = await response.blob()
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (e) {
let a = document.createElement('a');
a.href = (e.target?.result?.toString() ?? '');
a.download = requestFileName;
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
a.click();
a.remove();
};
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onload = (e) => {
const a = document.createElement('a')
a.href = (e.target?.result?.toString() ?? '')
a.download = requestFileName
document.body.appendChild(a) // we need to append the element to the dom -> otherwise it will not work in firefox
a.click()
a.remove()
}
}
export const upload = async (url:string, formData: FormData) => {
let response = await fetch(url, {
export const upload = async (url: string, formData: FormData) => {
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ' + localStorage['token']
Authorization: `Bearer ${localStorage['token']}`
},
method: 'Post',
body: formData,
@ -107,28 +100,11 @@ export const downloadFile = async (fileInfo: FileInfoDto) => {
}
}
export const formatBytes = (bytes:number) => {
export const formatBytes = (bytes: number) => {
if(bytes < 1024)
return `${bytes.toFixed(0)}b`
if(bytes < 1024*1024)
return `${(bytes/1024).toFixed(2)}kb`
if(bytes < 1024 * 1024)
return `${(bytes / 1024).toFixed(2)}kb`
else
return `${(bytes/1024/1024).toFixed(2)}Mb`
return `${(bytes / 1024 / 1024).toFixed(2)}Mb`
}
export const formatTimespan = (seconds:number) => {
const days = Math.floor(seconds / 86400)
seconds = seconds % 86400
const hours = Math.floor(seconds / 3600)
seconds = seconds % 3600
const minutes = Math.floor(seconds / 60)
seconds = seconds % 60
let formatedTimespan = ''
if(days > 0)
formatedTimespan += days + ' '
formatedTimespan += hours.toString().padStart(2,'0') + ':' +
minutes.toString().padStart(2,'0') + ':' +
seconds.toString().padStart(2,'0')
return formatedTimespan
}

View File

@ -55,6 +55,13 @@ export default function ClusterWells({statsWells}) {
if (!filtersWellsType.some((el) => el.text === well.wellType))
filtersWellsType.push({ text: well.wellType, value: well.wellType,})
let periodPlanValue = well.total?.plan?.start && well.total?.plan?.end
? (new Date(well.total?.plan?.end) - new Date(well.total?.plan?.start)) / DAY_IN_MS
: '-'
let periodFactValue = well.total?.fact?.start && well.total?.fact?.end
? (new Date(well.total?.fact?.end) - new Date(well.total?.fact?.start)) / DAY_IN_MS
: '-'
return {
key: well.caption,
id: well.id,
@ -62,8 +69,8 @@ export default function ClusterWells({statsWells}) {
wellType: well.wellType,
factStart: well.total?.fact?.start,
factEnd: well.total?.fact?.end,
periodPlan: (new Date(well.total?.plan?.end) - new Date(well.total?.plan?.start)) / DAY_IN_MS,
periodFact: (new Date(well.total?.fact?.end) - new Date(well.total?.fact?.start)) / DAY_IN_MS,
periodPlan: periodPlanValue,
periodFact: periodFactValue,
rateOfPenetrationPlan: well.total?.plan?.rop,
rateOfPenetrationFact: well.total?.fact?.rop,
routeSpeedPlan: well.total?.plan?.routeSpeed,
@ -91,7 +98,9 @@ export default function ClusterWells({statsWells}) {
setTableData(data)
}, [statsWells])
const getDate = (str) => Number.isNaN(+new Date(str)) ? '-' : new Date(str).toLocaleString()
const getDate = (str) => Number.isNaN(+new Date(str)) || +new Date(str) === 0
? '-'
: new Date(str).toLocaleString()
const columns = [
makeTextColumn('скв №', 'caption', null, null,
@ -110,11 +119,11 @@ export default function ClusterWells({statsWells}) {
makeTextColumn('Тип скв.', 'wellType', filtersWellsType, null, (text) => text ?? '-'),
makeGroupColumn('Фактические сроки', [
makeColumn('начало', 'factStart', { sorter: makeDateSorter('factStart'), render: getDate }),
makeColumn('окончание', 'factEnd', { sorter: makeDateSorter('factEnd'), render: getDate })
makeColumn('окончание', 'factEnd', { sorter: makeDateSorter('factEnd'), render: getDate }),
]),
makeNumericColumnPlanFact('Продолжительность', 'period', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('МСП', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Рейсовая скорость', 'routeSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Продолжительность, сут', 'period', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('МСП, м/ч', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Рейсовая скорость, м/ч', 'routeSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('НПВ, сут', 'notProductiveTime', filtersMinMax, makeFilterMinMaxFunction),
{
title: 'TVD',

View File

@ -1,29 +1,38 @@
import React, { useEffect, useState } from 'react'
import { Modal } from 'antd'
import { memo, useEffect, useState } from 'react'
import { Table } from '../../components/Table'
import { formatDate } from './MeasureTable'
import { formatDate } from '../../utils'
import { v } from './columnsCommon'
export const InclinometryTable = React.memo(({ group, visible, onClose }) => {
const tableScroll = { y: 400, x: 1300 }
const dateColumn = {
title: 'Дата',
key: 'date',
dataIndex: 'date',
render: (date) => formatDate(date, true) ?? 'Нет данных',
fixed: 'left',
width: '8rem',
}
export const InclinometryTable = memo(({ group, visible, onClose }) => {
const [tableColumns, setTableColumns] = useState([])
const [tableData, setTableData] = useState([])
useEffect(() => {
setTableColumns([{
title: 'Дата',
key: 'date',
dataIndex: 'date',
render: (item) => formatDate(item),
fixed: 'left',
width: '8rem',
},
...(group?.columns?.map((column) => ({...column, title: v(column.title)})) ?? [])
])
}, [group?.columns])
useEffect(() => setTableColumns([
dateColumn,
...(group?.columns?.map((column) => ({
...column,
title: v(column.title)
})) ?? [])
]), [group?.columns])
useEffect(() => {
setTableData(group?.values?.map(row => ({ date: row.timestamp, ...row.data })))
}, [group?.values])
useEffect(() => setTableData(group?.values?.map(row => ({
date: row.timestamp,
...row.data
}))), [group?.values])
return !group?.columns ? null : (
<Modal
@ -37,14 +46,9 @@ export const InclinometryTable = React.memo(({ group, visible, onClose }) => {
<Table
dataSource={tableData}
columns={tableColumns}
scroll={{
y: 400,
x: 1300
}}
scroll={tableScroll}
bordered
/>
</Modal>
)
})
export default InclinometryTable

View File

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react'
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
import moment from 'moment'
import {
CheckSquareOutlined,
EditOutlined,
@ -9,15 +8,19 @@ import {
CloseCircleOutlined,
DeleteOutlined
} from '@ant-design/icons'
import { View } from './View'
import LoaderPortal from '../../components/LoaderPortal'
import { MeasureService } from '../../services/api'
import '../../styles/index.css'
import '../../styles/measure.css'
import { invokeWebApiWrapperAsync } from '../../components/factory'
const dateFormat = 'YYYY.MM.DD HH:mm'
export const formatDate = (date) => date ? moment.utc(date).local().format(dateFormat) : 'Нет данных'
import { View } from './View'
import '../../styles/index.css'
import '../../styles/measure.css'
import { formatDate } from '../../utils'
const createEditingColumns = (cols, renderDelegate) =>
cols.map(col => ({ render: renderDelegate, ...col }))
export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButtons}) => {
const [showLoader, setShowLoader] = useState(false)
@ -29,11 +32,10 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
const [measuresForm] = Form.useForm()
const createEditingColumns = (cols, renderDelegate) =>
cols.map(col => ({ render: renderDelegate, ...col }))
useEffect(() => {
const data = [group.defaultValue].concat(group.values ?? [])
let data = [group.defaultValue]
if (group?.values?.length > 0)
data = group.values
setData(data)
setDisplayedValues(data.at(-1))
}, [group.defaultValue, group.values])
@ -118,8 +120,8 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
<Button key={'edit'} className={'flex-1'} onClick={() => editTable('edit')} disabled={isDataDefault()}>
<EditOutlined />
</Button>
<Popconfirm style={{flex: '1'}} title={'Удалить данные?'} onConfirm={markMeasuresAsDeleted} disabled={isDataDefault()}>
<Button key={'delete'} onClick={() => setEditingActionName('delete')} disabled={isDataDefault()} >
<Popconfirm style={{ flex: '1' }} title={'Удалить данные?'} onConfirm={markMeasuresAsDeleted} disabled={isDataDefault()}>
<Button key={'delete'} onClick={() => setEditingActionName('delete')} disabled={isDataDefault()}>
<DeleteOutlined style={{ margin:'auto 28px' }}/>
</Button>
</Popconfirm>
@ -136,12 +138,12 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
key={index}
className={'measure-button'}
onClick={() => setDisplayedValues(item)}
dot={item?.id !== displayedValues?.id ? null :
dot={item?.id === displayedValues?.id &&
<CheckSquareOutlined className={'timeline-clock-icon'} />
}
>
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}>
{formatDate(item.timestamp)}
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : undefined}>
{formatDate(item.timestamp, true) ?? 'Нет данных'}
</span>
</Timeline.Item>
)}

View File

@ -1,61 +1,50 @@
import React from 'react'
import { memo, Fragment } from 'react'
import { Empty, Form } from 'antd'
import { Grid, GridItem } from '../../components/Grid'
import '../../styles/index.css'
import '../../styles/measure.css'
const colsCount = 3
const headerCellStyle = {
border:'1px solid lightgrey'
}
export const View = memo(({ columns, item }) => !item || !columns?.length ? (
<Empty key={'empty'} image={Empty.PRESENTED_IMAGE_SIMPLE} />
) : (
<Grid>
{columns.map((column, i) => (
<Fragment key={i}>
<GridItem
key={column.dataIndex}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 1}
className={'measure-column-header'}
>
{column.title}
</GridItem>
const valueCellStyle = {
border:'1px solid lightgrey',
justifyContent:'right',
marginRight:'16px',
fontWeight:'bold',
textAlign:'right',
padding: 0
}
export const View = React.memo(({columns, item}) => {
if (!item || !columns?.length)
return <Empty key='empty' image={Empty.PRESENTED_IMAGE_SIMPLE}/>
return (
<Grid>
{columns.map((column, i) => (
<>
<GridItem
key={column.dataIndex}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 1}
style={headerCellStyle}
>
{column.title}
</GridItem>
<GridItem
key={column.title}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 2}
style={valueCellStyle}
>
{column.render ? (
<Form.Item
key={column.dataIndex}
name={column.dataIndex}
style={{ margin: 0 }}
//rules={column.formItemRules}
>
{column.render(item[column.dataIndex])}
</Form.Item>
) : (
<p key={column.title} className='m-5px'>{item[column.dataIndex]}</p>
)}
</GridItem>
</>
))}
</Grid>
)
})
<GridItem
key={column.title}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 2}
className={'measure-column-value'}
style={{ padding: 0 }}
>
{column.render ? (
<Form.Item
key={column.dataIndex}
name={column.dataIndex}
style={{ margin: 0 }}
>
{column.render(item[column.dataIndex])}
</Form.Item>
) : (
<p key={column.title} className={'m-5px'}>
{item[column.dataIndex]}
</p>
)}
</GridItem>
</Fragment>
))}
</Grid>
))

View File

@ -1,27 +1,12 @@
import React from 'react'
import { Input } from 'antd'
import { RegExpIsFloat } from '../../components/Table'
const { TextArea } = Input
import '../../styles/measure.css'
export const v = (text) => (
<div style={{
display: 'flex',
height: '180px',
justifyContent: 'center'
}}>
<span
style={{
whiteSpace: 'pre',
verticalAlign: 'center',
textAlign: 'center',
WebkitTransform: 'rotate(-90deg)',
MozTransform: 'rotate(-90deg)',
MsTransform: 'rotate(-90deg)',
OTransform: 'rotate(-90deg)',
transform: 'rotate(-90deg)',
}}>
<div className={'v-div'}>
<span className={'v-span'}>
{text}
</span>
</div>
@ -42,7 +27,7 @@ export const numericColumnOptions = {
export const textColumnOptions = {
editable: true,
input: <TextArea/>,
input: <Input.TextArea/>,
width: '3rem',
formItemRules: [{
required: true,

View File

@ -1,36 +1,37 @@
import { makeColumn } from '../../components/Table';
import { makeColumn } from '../../components/Table'
import { numericColumnOptions, textColumnOptions } from './columnsCommon'
export const columnsDrillingFluid = [
makeColumn('Наименование', 'name', textColumnOptions),
makeColumn('Температура, °C', 'temperature',numericColumnOptions),
makeColumn('Плотность, г/см³', 'density',numericColumnOptions),
makeColumn('Усл. вязкость, сек', 'conditionalViscosity',numericColumnOptions),
makeColumn('R300', 'r300',numericColumnOptions),
makeColumn('R600', 'r600',numericColumnOptions),
makeColumn('R3/R6', 'r3r6',numericColumnOptions),
makeColumn('ДНС, дПа', 'dnsDpa',numericColumnOptions),
makeColumn('Пластич. вязкость, сПз', 'plasticViscocity',numericColumnOptions),
makeColumn('СНС, дПа', 'snsDpa',numericColumnOptions),
makeColumn('R3/R6 49С', 'r3r649С',numericColumnOptions),
makeColumn('ДНС 49С, дПа', 'dns49Cdpa',numericColumnOptions),
makeColumn('Пластич. вязкость 49С, сПз', 'plasticViscocity49c',numericColumnOptions),
makeColumn('СНС 49С, дПа', 'sns49Cdpa',numericColumnOptions),
makeColumn('МВТ, кг/м³', 'mbt',numericColumnOptions),
makeColumn('Песок, %', 'sand',numericColumnOptions),
makeColumn('Фильтрация, см³/30мин', 'filtering',numericColumnOptions),
makeColumn('Корка, мм', 'crust',numericColumnOptions),
makeColumn('KTK', 'ktk',numericColumnOptions),
makeColumn('pH', 'ph',numericColumnOptions),
makeColumn('Жесткость, мг/л', 'hardness',numericColumnOptions),
makeColumn('Хлориды, мг/л', 'chlorides',numericColumnOptions),
makeColumn('PF', 'pf',numericColumnOptions),
makeColumn('Mf', 'mf',numericColumnOptions),
makeColumn('Pm', 'pm',numericColumnOptions),
makeColumn('Твердая фаза раствора, %', 'fluidSolidPhase',numericColumnOptions),
makeColumn('Смазка, %', 'grease',numericColumnOptions),
makeColumn('Карбонат кальция, кг/м³', 'calciumCarbonate',numericColumnOptions),
];
makeColumn('Температура, °C', 'temperature', numericColumnOptions),
makeColumn('Плотность, г/см³', 'density', numericColumnOptions),
makeColumn('Усл. вязкость, сек', 'conditionalViscosity', numericColumnOptions),
makeColumn('R300', 'r300', numericColumnOptions),
makeColumn('R600', 'r600', numericColumnOptions),
makeColumn('R3/R6', 'r3r6', numericColumnOptions),
makeColumn('ДНС, дПа', 'dnsDpa', numericColumnOptions),
makeColumn('Пластич. вязкость, сПз', 'plasticViscocity', numericColumnOptions),
makeColumn('СНС, дПа', 'snsDpa', numericColumnOptions),
makeColumn('R3/R6 49С', 'r3r649С', numericColumnOptions),
makeColumn('ДНС 49С, дПа', 'dns49Cdpa', numericColumnOptions),
makeColumn('Пластич. вязкость 49С, сПз', 'plasticViscocity49c', numericColumnOptions),
makeColumn('СНС 49С, дПа', 'sns49Cdpa', numericColumnOptions),
makeColumn('МВТ, кг/м³', 'mbt', numericColumnOptions),
makeColumn('Песок, %', 'sand', numericColumnOptions),
makeColumn('Фильтрация, см³/30мин', 'filtering', numericColumnOptions),
makeColumn('Корка, мм', 'crust', numericColumnOptions),
makeColumn('KTK', 'ktk', numericColumnOptions),
makeColumn('pH', 'ph', numericColumnOptions),
makeColumn('Жесткость, мг/л', 'hardness', numericColumnOptions),
makeColumn('Хлориды, мг/л', 'chlorides', numericColumnOptions),
makeColumn('PF', 'pf', numericColumnOptions),
makeColumn('Mf', 'mf', numericColumnOptions),
makeColumn('Pm', 'pm', numericColumnOptions),
makeColumn('Твердая фаза раствора, %', 'fluidSolidPhase', numericColumnOptions),
makeColumn('Смазка, %', 'grease', numericColumnOptions),
makeColumn('Карбонат кальция, кг/м³', 'calciumCarbonate', numericColumnOptions),
]
export const drillingFluidDefaultData = {
idWell: 0,
@ -38,33 +39,33 @@ export const drillingFluidDefaultData = {
idCategory: 0,
isDefaultData: true,
data: {
'name': 0,
'temperature': 0,
'density': 0,
'conditionalViscosity': 0,
'r300': 0,
'r600': 0,
'r3r6': 0,
'dnsDpa': 0,
'plasticViscocity': 0,
'snsDpa': 0,
'r3r649С': 0,
'dns49Cdpa': 0,
'plasticViscocity49c': 0,
'sns49Cdpa': 0,
'mbt': 0,
'sand': 0,
'filtering': 0,
'crust': 0,
'ktk': 0,
'ph': 0,
'hardness': 0,
'chlorides': 0,
'pf': 0,
'mf': 0,
'pm': 0,
'fluidSolidPhase': 0,
'grease': 0,
'calciumCarbonate': 0
name: 0,
temperature: 0,
density: 0,
conditionalViscosity: 0,
r300: 0,
r600: 0,
r3r6: 0,
dnsDpa: 0,
plasticViscocity: 0,
snsDpa: 0,
r3r649С: 0,
dns49Cdpa: 0,
plasticViscocity49c: 0,
sns49Cdpa: 0,
mbt: 0,
sand: 0,
filtering: 0,
crust: 0,
ktk: 0,
ph: 0,
hardness: 0,
chlorides: 0,
pf: 0,
mf: 0,
pm: 0,
fluidSolidPhase: 0,
grease: 0,
calciumCarbonate: 0
}
}

View File

@ -1,47 +1,55 @@
import { Button } from 'antd'
import { useState, useEffect } from 'react'
import { TableOutlined } from '@ant-design/icons'
import { MeasureService } from '../../services/api'
import LoaderPortal from '../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { MeasureTable } from './MeasureTable'
import { InclinometryTable } from './InclinometryTable'
import { columnsNnb, nnbDefaultData } from './nnbData'
import { columnsMudDiagram, mudDiagramDefaultData } from './mudDiagramData'
import { columnsDrillingFluid, drillingFluidDefaultData } from './drillingFluidData'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { MeasureService } from '../../services/api'
import LoaderPortal from '../../components/LoaderPortal'
import { MeasureTable } from './MeasureTable'
import InclinometryTable from './InclinometryTable'
import { Button } from 'antd'
import { TableOutlined } from '@ant-design/icons'
const defaultData = [
{
idCategory: 1,
title: 'Замер бурового раствора',
columns: columnsDrillingFluid,
values: [drillingFluidDefaultData],
defaultValue: drillingFluidDefaultData,
},
{
}, {
idCategory: 2,
title: 'Шламограмма',
columns: columnsMudDiagram,
values: [mudDiagramDefaultData],
defaultValue: mudDiagramDefaultData,
},
{
}, {
idCategory: 3,
title: 'ННБ',
columns: columnsNnb,
values: [nnbDefaultData],
defaultValue: nnbDefaultData,
additionalButtons: (group, idx, onClick) => (isEditing) => isEditing ? null : (
<Button
key={'table'}
className={'flex-1'}
onClick={() => onClick(idx)}
disabled={!group.values || group.values.length <= 0}
>
<TableOutlined />
</Button>
)
}
]
export default function Measure({idWell}){
export const Measure = ({ idWell }) => {
const [showLoader, setShowLoader] = useState(false)
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(false)
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(true)
const [data, setData] = useState(defaultData)
const [tableIdx, setTableIdx] = useState(-1)
const updateCurrentValues = () => invokeWebApiWrapperAsync(
useEffect(() => invokeWebApiWrapperAsync(
async () => {
if (!isMeasuresUpdating) return
const measures = await MeasureService.getHisory(idWell)
setIsMeasuresUpdating(false)
@ -57,28 +65,9 @@ export default function Measure({idWell}){
},
setShowLoader,
`Не удалось загрузить последние данные по скважине ${idWell}`
)
), [idWell, isMeasuresUpdating])
useEffect(() => {
setData(prevData => {
prevData[2].additionalButtons = (group, idx) => (isEditing) => isEditing ? null : (
<Button
key={'table'}
className={'flex-1'}
onClick={() => setTableIdx(idx)}
disabled={!group.values || group.values.length <= 0}
>
<TableOutlined />
</Button>
)
return prevData
})
}, [])
useEffect(updateCurrentValues, [idWell, isMeasuresUpdating])
return <>
return (
<LoaderPortal show={showLoader}>
{data.map((group, idx) => (
<MeasureTable
@ -86,7 +75,7 @@ export default function Measure({idWell}){
idWell={idWell}
group={group}
updateMeasuresFunc={() => setIsMeasuresUpdating(true)}
additionalButtons={group.additionalButtons?.(group, idx)}
additionalButtons={group.additionalButtons?.(group, idx, setTableIdx)}
/>
))}
<InclinometryTable
@ -95,5 +84,7 @@ export default function Measure({idWell}){
group={data[tableIdx]}
/>
</LoaderPortal>
</>
)
}
export default Measure

View File

@ -32,27 +32,27 @@ export const mudDiagramDefaultData = {
idCategory: 0,
isDefaultData: true,
data: {
'probeNumber': 0,
'probeExtractionDepth': 0,
'sandstone': 0,
'siltstone': 0,
'argillit': 0,
'brokenArgillit': 0,
'coal': 0,
'sand': 0,
'clay': 0,
'camstone': 0,
'cement': 0,
'summary': '-',
'drillingMud': 0,
'sludge': 0,
'maxSum': 0,
'methane': 0,
'ethane': 0,
'propane': 0,
'butane': 0,
'pentane': 0,
'mechanicalSpeed': 0,
'preliminaryConclusion': '-'
probeNumber: 0,
probeExtractionDepth: 0,
sandstone: 0,
siltstone: 0,
argillit: 0,
brokenArgillit: 0,
coal: 0,
sand: 0,
clay: 0,
camstone: 0,
cement: 0,
summary: '-',
drillingMud: 0,
sludge: 0,
maxSum: 0,
methane: 0,
ethane: 0,
propane: 0,
butane: 0,
pentane: 0,
mechanicalSpeed: 0,
preliminaryConclusion: '-'
}
}

View File

@ -19,7 +19,7 @@ export const columnsNnb = [
makeColumn('Комментарий', 'comment', numericColumnOptions),
makeColumn('Разница вертикальных глубин\nмежду планом и фактом', 'depthPlanFactDifference', numericColumnOptions),
makeColumn('Расстояние в пространстве\nмежду планом и фактом', 'distancePlanFactDifference', numericColumnOptions),
];
]
export const nnbDefaultData = {
idWell: 0,
@ -27,22 +27,22 @@ export const nnbDefaultData = {
idCategory: 0,
isDefaultData: true,
data: {
'depth': 0,
'zenithAngle': 0,
'magneticAzimuth': 0,
'trueAzimuth': 0,
'directAzimuth': 0,
'verticalDepth': 0,
'absoluteMark': 0,
'localNorthOffset': 0,
'localEastOffset': 0,
'outFallOffset': 0,
'offsetAzimuth': 0,
'areaIntensity': '-',
'offsetStopAngle': 0,
'zenithIntensity': 0,
'comment': '-',
'depthPlanFactDifference': 0,
'distancePlanFactDifference': 0
depth: 0,
zenithAngle: 0,
magneticAzimuth: 0,
trueAzimuth: 0,
directAzimuth: 0,
verticalDepth: 0,
absoluteMark: 0,
localNorthOffset: 0,
localEastOffset: 0,
outFallOffset: 0,
offsetAzimuth: 0,
areaIntensity: '-',
offsetStopAngle: 0,
zenithIntensity: 0,
comment: '-',
depthPlanFactDifference: 0,
distancePlanFactDifference: 0
}
}

View File

@ -1,81 +1,83 @@
import { useState, useEffect } from "react"
import { Table, formatDate, makeDateSorter, makeNumericSorter} from "../../components/Table"
import { Button, Tooltip } from "antd"
import { FilePdfOutlined, FileTextOutlined} from '@ant-design/icons'
import { ReportService } from "../../services/api"
import { invokeWebApiWrapperAsync, downloadFile, formatTimespan} from "../../components/factory"
import LoaderPortal from "../../components/LoaderPortal"
import moment from "moment"
import { Button, Tooltip } from 'antd'
import { useState, useEffect } from 'react'
import { FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
import { ReportService } from '../../services/api'
import { formatDate, periodToString } from '../../utils'
import LoaderPortal from '../../components/LoaderPortal'
import { Table, makeDateSorter, makeNumericSorter } from '../../components/Table'
import { invokeWebApiWrapperAsync, downloadFile } from '../../components/factory'
const imgPaths = {
".pdf": <FilePdfOutlined/>,
".las": <FileTextOutlined />,
'.pdf': <FilePdfOutlined/>,
'.las': <FileTextOutlined/>,
}
const columns = [
const columns = [
{
title: "Название",
dataIndex: "name",
key: "name",
title: 'Название',
dataIndex: 'name',
key: 'name',
render: (name, report) => (
<Button
type="link"
type={'link'}
icon={imgPaths[report.format]}
onClick={(_) => downloadFile(report.file)}
onClick={() => downloadFile(report.file)}
download={name}
>
{name}
</Button>
),
},
{
title: <Tooltip title="Дата формирования">Сформирован</Tooltip>,
dataIndex: "date",
key: "date",
}, {
title: <Tooltip title={'Дата формирования'}>Сформирован</Tooltip>,
dataIndex: 'date',
key: 'date',
sorter: makeDateSorter('date'),
render: date=>moment(date).format(formatDate),
},
{
title: <Tooltip title="Дата начала периода рапорта">С</Tooltip>,
dataIndex: "begin",
key: "begin",
render: (date) => formatDate(date),
}, {
title: <Tooltip title={'Дата начала периода рапорта'}>С</Tooltip>,
dataIndex: 'begin',
key: 'begin',
sorter: makeDateSorter('begin'),
render: date=>moment(date).format(formatDate),
},
{
title: <Tooltip title="Дата окончания периода рапорта">По</Tooltip>,
dataIndex: "end",
key: "end",
render: (date) => formatDate(date),
}, {
title: <Tooltip title={'Дата окончания периода рапорта'}>По</Tooltip>,
dataIndex: 'end',
key: 'end',
sorter: makeDateSorter('end'),
render: date=>moment(date).format(formatDate),
},
{
title: <Tooltip title="шаг сетки графиков">шаг, сек</Tooltip>,
dataIndex: "step",
key: "step",
render: (date) => formatDate(date),
}, {
title: <Tooltip title={'шаг сетки графиков'}>шаг, сек</Tooltip>,
dataIndex: 'step',
key: 'step',
sorter: makeNumericSorter('step'),
render: step => formatTimespan(step)
render: step => periodToString(step),
},
]
export const Reports = ({idWell}) => {
export const Reports = ({ idWell }) => {
const [reports, setReports] = useState([])
const [showLoader, setShowLoader] = useState(false)
useEffect(()=>invokeWebApiWrapperAsync(async()=>{
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
const reports = reportsResponse.map(r => ({...r, key: r.id?? r.name?? r.date}))
setReports(reports)
},setShowLoader), [idWell])
useEffect(() => invokeWebApiWrapperAsync(
async () => {
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
const reports = reportsResponse.map(r => ({ ...r, key: r.id ?? r.name ?? r.date }))
setReports(reports)
},
setShowLoader,
`Не удалось загрузить список рапортов по скважине "${idWell}"`
), [idWell])
return (
<LoaderPortal show={showLoader}>
<Table
size='small' bordered
<Table
size={'small'}
bordered
columns={columns}
dataSource={reports}
pagination={{pageSize:13}}/>
</LoaderPortal>)
}
pagination={{ pageSize: 13 }}
/>
</LoaderPortal>
)
}

View File

@ -1,63 +1,55 @@
import { useState, useEffect } from "react"
import { DatePicker, Radio, Button, Select, notification } from "antd"
import "moment/locale/ru"
import moment from "moment"
import { Subscribe } from "../../services/signalr"
import LoaderPortal from "../../components/LoaderPortal"
import { ReportService } from "../../services/api"
import { ReportCreationNotify } from "./ReportCreationNotify"
import { invokeWebApiWrapperAsync, notify } from "../../components/factory"
import { Reports } from "./Reports"
import 'moment/locale/ru'
import moment from 'moment'
import { useState, useEffect } from 'react'
import { DatePicker, Radio, Button, Select, notification } from 'antd'
const { RangePicker } = DatePicker
const { Option } = Select
import { ReportService } from '../../services/api'
import { Subscribe } from '../../services/signalr'
import LoaderPortal from '../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { Reports } from './Reports'
import { ReportCreationNotify } from './ReportCreationNotify'
const timePeriodNames = [
{ label: "1 секунда", value: 1 },
{ label: "10 секунд", value: 10 },
{ label: "1 минута", value: 60 },
{ label: "5 минут", value: 300 },
{ label: "30 минут", value: 1800 },
{ label: "1 час", value: 3600 },
{ label: "6 часов", value: 21600 },
{ label: "12 часов", value: 43200 },
{ label: "1 день", value: 86400 },
{ label: "1 неделя", value: 604800 },
{ label: '1 секунда', value: 1 },
{ label: '10 секунд', value: 10 },
{ label: '1 минута', value: 60 },
{ label: '5 минут', value: 300 },
{ label: '30 минут', value: 1800 },
{ label: '1 час', value: 3600 },
{ label: '6 часов', value: 21600 },
{ label: '12 часов', value: 43200 },
{ label: '1 день', value: 86400 },
{ label: '1 неделя', value: 604800 },
]
const dateTimeFormat = "DD.MM.YYYY hh:mm:ss"
const dateTimeFormat = 'DD.MM.YYYY hh:mm:ss'
const reportFormats = [
{ value: 0, label: 'PDF' },
{ value: 1, label: 'LAS' },
]
export default function Report({ idWell }) {
const [aviableDateRange, setAviableDateRange] = useState([
moment(),
moment(),
])
const [aviableDateRange, setAviableDateRange] = useState([moment(), moment()])
const [filterDateRange, setFilterDateRange] = useState([
moment().subtract(1, "days").startOf("day"),
moment().startOf("day"),
moment().subtract(1, 'days').startOf('day'),
moment().startOf('day'),
])
const [step, setStep] = useState(timePeriodNames[2].value)
const [format, setFormat] = useState(0)
const [pagesCount, setPagesCount] = useState(0)
const [showLoader, setShowLoader] = useState(false)
const periodOptions = timePeriodNames.map((item) => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))
const handleReportCreation = async () => {
let begin = filterDateRange[0].toISOString()
let end = filterDateRange[1].toISOString()
try {
const handleReportCreation = async () => await invokeWebApiWrapperAsync(
async () => {
const taskId = await ReportService.createReport(
idWell,
step,
format,
begin,
end
filterDateRange[0].toISOString(),
filterDateRange[1].toISOString()
)
if (!taskId) return
@ -65,12 +57,8 @@ export default function Report({ idWell }) {
if (progressData) {
notification.open({
key: taskId,
message: "Создание отчета:",
description: (
<ReportCreationNotify
progressData={progressData}
></ReportCreationNotify>
),
message: 'Создание отчета:',
description: <ReportCreationNotify progressData={progressData} />,
duration: 0,
onClose: unSubscribeReportHub,
})
@ -81,82 +69,68 @@ export default function Report({ idWell }) {
}
const unSubscribeReportHub = Subscribe(
"hubs/reports",
"GetReportProgress",
'hubs/reports',
'GetReportProgress',
`Report_${taskId}`,
handleReportProgress
)
} catch (error) {
notify(
`Не удалось создать отчет по скважине (${idWell}) c
${filterDateRange[0].format("DD.MM.YYYY hh:mm:ss")} по
${filterDateRange[1].format("DD.MM.YYYY hh:mm:ss")}`,
"error"
},
setShowLoader,
`Не удалось создать отчет по скважине (${idWell}) c
${filterDateRange[0].format(dateTimeFormat)} по
${filterDateRange[1].format(dateTimeFormat)}`
)
const disabledDate = (current) => !current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
useEffect(() => invokeWebApiWrapperAsync(
async () => {
const datesRangeResponse = await ReportService.getReportsDateRange(idWell)
if (!datesRangeResponse?.from || !datesRangeResponse.to)
throw new Error('Формат ответа неверный!')
const datesRange = [
moment(datesRangeResponse.from),
moment(datesRangeResponse.to),
]
setAviableDateRange(datesRange)
const from = moment.max(moment(datesRange[1]).subtract(1, 'days'), datesRange[0])
setFilterDateRange([
from.startOf('day'),
moment(datesRange[1]).startOf('day'),
])
},
setShowLoader,
`Не удалось получить диапозон дат рапортов для скважины "${idWell}"`
), [idWell])
useEffect(() => invokeWebApiWrapperAsync(
async () => {
if (filterDateRange?.length !== 2) return
const pagesCount = await ReportService.getReportSize(
idWell,
step,
format,
filterDateRange[0].toISOString(),
filterDateRange[1].toISOString()
)
console.log(error)
}
}
const disabledDate = (current) => {
return current < aviableDateRange[0] || current > aviableDateRange[1]
}
useEffect(
() =>
invokeWebApiWrapperAsync(async () => {
const datesRangeResponse = await ReportService.getReportsDateRange(
idWell
)
const datesRange = [
moment(datesRangeResponse.from),
moment(datesRangeResponse.to),
]
setAviableDateRange(datesRange)
let from = moment(datesRangeResponse.to)
from = from.subtract(1, "days")
if (from < datesRange[0]) from = datesRange[0]
const filterDateDefaults = [
from.startOf("day"),
moment(datesRangeResponse.to).startOf("day"),
]
setFilterDateRange(filterDateDefaults)
}, setShowLoader),
[idWell]
)
useEffect(
() =>
invokeWebApiWrapperAsync(
async () => {
if (!filterDateRange || filterDateRange.length < 2) return
const begin = filterDateRange[0].toISOString()
const end = filterDateRange[1].toISOString()
const pagesCount = await ReportService.getReportSize(
idWell,
step,
format,
begin,
end
)
setPagesCount(pagesCount)
},
setShowLoader,
`Не удалось получить предварительные параметры отчета c
${filterDateRange[0].format(dateTimeFormat)} по
${filterDateRange[1].format(dateTimeFormat)}`
),
[filterDateRange, step, format, idWell]
)
setPagesCount(pagesCount)
},
setShowLoader,
`Не удалось получить предварительные параметры отчета c
${filterDateRange[0].format(dateTimeFormat)} по
${filterDateRange[1].format(dateTimeFormat)}`
), [filterDateRange, step, format, idWell])
return (
<div>
<LoaderPortal show={showLoader}>
<div className="w-100 mt-20px mb-20px d-flex">
<div className={'w-100 mt-20px mb-20px d-flex'}>
<div>
<div>Диапазон дат отчета</div>
<RangePicker
<DatePicker.RangePicker
disabledDate={disabledDate}
allowClear={false}
onCalendarChange={setFilterDateRange}
@ -164,38 +138,28 @@ export default function Report({ idWell }) {
value={filterDateRange}
/>
</div>
<div className="ml-30px">
<div className={'ml-30px'}>
<div>Шаг графиков</div>
<Select style={{ width: "8rem" }}
onChange={e=>{
setStep(e)
}}
value={step}>
{periodOptions}
</Select>
<Select
style={{ width: '8rem' }}
onChange={e => setStep(e)}
value={step}
options={timePeriodNames}
/>
</div>
<div className="ml-30px">
<div className={'ml-30px'}>
<div>Формат отчета</div>
<Radio.Group
onChange={(e) => setFormat(e.target.value)}
options={reportFormats}
value={format}
>
<Radio.Button value={0}>PDF</Radio.Button>
<Radio.Button value={1}>LAS</Radio.Button>
</Radio.Group>
/>
</div>
<div className="ml-30px">
<div className={'ml-30px'}>
<div>&nbsp;</div>
<Button type="primary" onClick={handleReportCreation}>
<span>Получить рапорт</span>
<span className={"ml-5px"}>({pagesCount} стр.)</span>
<span
style={{ display: pagesCount > 100 ? "inline" : "none" }}
className={"ml-5px"}
>
!!!
</span>
<Button type={'primary'} onClick={handleReportCreation}>
Получить рапорт ({pagesCount} стр.) {pagesCount > 100 && '!!!'}
</Button>
</div>
</div>

View File

@ -1,10 +1,10 @@
import moment from 'moment'
import { memo } from 'react'
import { Modal, Input } from 'antd'
import { Table } from '../../components/Table'
import { UserView } from '../../components/Views'
import { Grid, GridItem } from '../../components/Grid'
import { periodToString } from '../../utils/datetime'
import { formatDate, periodToString } from '../../utils'
export const setpointStatus = {
0: 'Неизвестно',
@ -16,9 +16,9 @@ export const setpointStatus = {
}
export const getSetpointStatus = (id) => {
if (!id || Object.keys(setpointStatus).every((idx) => Number(idx) !== id))
return setpointStatus[0]
return setpointStatus[id]
if (id && Object.keys(setpointStatus).some((idx) => Number(idx) === id))
return setpointStatus[id]
return setpointStatus[0]
}
const columns = [
@ -27,14 +27,11 @@ const columns = [
]
export const SetpointViewer = memo(({ setpoint, visible, onClose, setpointNames }) => {
let date = moment(setpoint?.uploadDate).format('DD MMM YYYY, HH:mm:ss')
let setpoints = []
if (setpoint) {
setpoints = Object.keys(setpoint?.setpoints).map((name) => ({
name: setpointNames.find(spName => spName.value === name)?.label,
value: setpoint.setpoints[name]
}))
}
const date = formatDate(setpoint?.uploadDate)
const setpoints = Object.keys(setpoint?.setpoints ?? []).map((name) => ({
name: setpointNames.find(spName => spName.value === name)?.label,
value: setpoint?.setpoints?.[name]
}))
return (
<Modal
@ -55,7 +52,7 @@ export const SetpointViewer = memo(({ setpoint, visible, onClose, setpointNames
<GridItem row={3} col={2}>{getSetpointStatus(setpoint?.idState)}</GridItem>
<GridItem row={4} col={1}>Период актуальности рекомендаций:</GridItem>
<GridItem row={4} col={2}>{periodToString(setpoint?.obsolescenceSec)}</GridItem>
<GridItem row={4} col={2}>{periodToString(setpoint?.obsolescenceSec * 1000)}</GridItem>
<GridItem row={5} col={1}>Комментарий:</GridItem>
<GridItem row={6} col={1} colSpan={3}>

View File

@ -122,13 +122,13 @@ export const WellCompositeSections = ({idWell, statsWells, selectedSections}) =>
(text, item) => <Link to={`/well/${item?.id}`}>{text ?? '-'}</Link>
),
makeTextColumn('Секция', 'sectionType', filtersSectionsType, null, (text) => text ?? '-'),
makeNumericColumnPlanFact('Глубина', 'sectionWellDepth', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Продолжительность', 'sectionBuildDays', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('МСП', 'sectionRateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Рейсовая скорость', 'sectionRouteSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Спуск КНБК', 'sectionBhaDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Подъем КНБК', 'sectionBhaUpSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Скорость спуска ОК', 'sectionCasingDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Глубина, м', 'sectionWellDepth', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Продолжительность, ч', 'sectionBuildDays', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('МСП, м/ч', 'sectionRateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Рейсовая скорость, м/ч', 'sectionRouteSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Спуск КНБК, м/ч', 'sectionBhaDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Подъем КНБК, м/ч', 'sectionBhaUpSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Скорость спуска ОК, м/ч', 'sectionCasingDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('НПВ, сут', 'nonProductiveTime', filtersMinMax, makeFilterMinMaxFunction, null, '70px'),
{
title: 'TVD',

View File

@ -1,31 +1,28 @@
import { useState, useEffect } from 'react'
import {
makeColumn,
makeNumericAvgRange
} from '../../components/Table'
import { DrillParamsService } from '../../services/api'
import LoaderPortal from '../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { EditableTable, SelectFromDictionary } from '../../components/Table'
import { DrillParamsService } from '../../services/api'
import { makeActionHandler, makeColumn, makeNumericAvgRange } from '../../components/Table'
import { dictionarySectionType, getByKeyOrReturnKey } from './dictionary'
export const columns = [
makeColumn('Конструкция секции','idWellSectionType', {
editable:true,
input:<SelectFromDictionary dictionary={dictionarySectionType}/>,
width:160,
render:(_, record)=>getByKeyOrReturnKey(dictionarySectionType, record.idWellSectionType)
editable: true,
input: <SelectFromDictionary dictionary={dictionarySectionType}/>,
width: 160,
render: (_, record) => getByKeyOrReturnKey(dictionarySectionType, record.idWellSectionType)
}),
// makeNumericStartEnd('Глубина', 'depth'),
makeNumericAvgRange('Нагрузка', 'axialLoad', 1),
makeNumericAvgRange('Давление', 'pressure', 1),
makeNumericAvgRange('Момент на ВСП', 'rotorTorque', 1),
makeNumericAvgRange('Обороты на ВСП', 'rotorSpeed', 1),
makeNumericAvgRange('Расход', 'flow', 1)
makeNumericAvgRange('Нагрузка, т', 'axialLoad', 1),
makeNumericAvgRange('Давление, атм', 'pressure', 1),
makeNumericAvgRange('Момент на ВСП, кН·м', 'rotorTorque', 1),
makeNumericAvgRange('Обороты на ВСП, об/мин', 'rotorSpeed', 1),
makeNumericAvgRange('Расход, л/с', 'flow', 1)
]
export const WellDrillParams = ({idWell}) => {
export const WellDrillParams = ({ idWell }) => {
const [params, setParams] = useState([])
const [showLoader, setShowLoader] = useState(false)
@ -40,24 +37,15 @@ export const WellDrillParams = ({idWell}) => {
useEffect(updateParams, [idWell])
const onAdd = async (param) => {
param.idWell = idWell
await DrillParamsService.insert(idWell, param)
updateParams()
const handlerProps = {
service: DrillParamsService,
setLoader: setShowLoader,
errorMsg: `Не удалось выполнить операцию`,
onComplete: updateParams,
idWell
}
const onEdit = async (param) => {
if (!param.id) return
param.idWell = idWell
await DrillParamsService.update(idWell, param.id, param)
updateParams()
}
const onDelete = async (param) => {
if (!param.id) return
await DrillParamsService.delete(idWell, param.id)
updateParams()
}
const recordParser = (record) => ({ ...record, idWell })
return (
<LoaderPortal show={showLoader}>
@ -66,9 +54,9 @@ export const WellDrillParams = ({idWell}) => {
bordered
columns={columns}
dataSource={params}
onRowAdd={onAdd}
onRowEdit={onEdit}
onRowDelete={onDelete}
onRowAdd={makeActionHandler('insert', handlerProps, recordParser)}
onRowEdit={makeActionHandler('update', handlerProps, recordParser)}
onRowDelete={makeActionHandler('delete', handlerProps, recordParser)}
pagination={false}
/>
</LoaderPortal>

View File

@ -1,48 +1,82 @@
import { useState, useEffect } from 'react'
import { Input } from 'antd'
import moment from 'moment'
import {
EditableTable,
DatePickerWrapper,
SelectFromDictionary,
makeColumn,
import { Input } from 'antd'
import { useState, useEffect } from 'react'
import {
EditableTable,
DatePickerWrapper,
makeColumn,
makeDateSorter,
makeNumericColumnOptions} from "../../components/Table"
makeNumericColumnOptions,
makeSelectColumn,
makeActionHandler,
} from '../../components/Table'
import LoaderPortal from '../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { WellOperationService} from '../../services/api'
import { dictionarySectionType, getByKeyOrReturnKey } from './dictionary'
import { formatDate } from '../../utils'
const { TextArea } = Input;
const { TextArea } = Input
const basePageSize = 160;
const format='YYYY.MM.DD HH:mm'
const basePageSize = 160
const defaultColumns = [
makeSelectColumn('Конструкция секции', 'idWellSectionType', [], undefined, { editable: true, width: 160 }),
makeSelectColumn('Операция', 'idCategory', [], undefined, { editable: true, width: 200 }),
makeColumn('Доп. инфо', 'categoryInfo', { editable: true, width: 300, input: <TextArea/> }),
makeColumn('Глубина забоя на начало, м', 'depthStart', makeNumericColumnOptions(2, 'depthStart')),
makeColumn('Глубина забоя при завершении, м', 'depthEnd', makeNumericColumnOptions(2, 'depthEnd')),
makeColumn('Время начала', 'dateStart', {
editable: true,
width: 200,
input: <DatePickerWrapper/>,
initialValue: moment().format(),
sorter: makeDateSorter('dateStart'),
render: (_, record) => (
<div className={'text-align-r-container'}>
<span>{formatDate(record.dateStart)}</span>
</div>
)
}),
makeColumn('Часы', 'durationHours', makeNumericColumnOptions(2, 'durationHours')),
makeColumn('Комментарий', 'comment', { editable: true, input: <TextArea/> }),
]
export const WellOperationsEditor = ({idWell, idType}) => {
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({current:1, pageSize:basePageSize})
const [paginationTotal, setPaginationTotal] = useState(0)
const [dictionaryOperationCategory, setDictionaryOperationCategory] = useState(new Map())
const [operations, setOperations] = useState([])
const [showLoader, setShowLoader] = useState(false)
const [columns, setColumns] = useState(defaultColumns)
useEffect(() => invokeWebApiWrapperAsync(
async () => {
const categories = await WellOperationService.getCategories(idWell)
const dictCategories = new Map()
categories?.forEach((item) => dictCategories.set(item.id, item.name))
setDictionaryOperationCategory(dictCategories)
}),[idWell])
let categories = await WellOperationService.getCategories(idWell) ?? []
categories = categories.map((item) => ({ value: item.id, label: item.name }))
let sectionTypes = await WellOperationService.getSectionTypes(idWell) ?? []
sectionTypes = Object.keys(sectionTypes).map((key) => ({ value: parseInt(key), label: sectionTypes[key] }))
setColumns(preColumns => {
preColumns[0] = makeSelectColumn('Конструкция секции', 'idWellSectionType', sectionTypes, undefined, { editable: true, width: 160 })
preColumns[1] = makeSelectColumn('Операция', 'idCategory', categories, undefined, { editable: true, width: 200 })
return preColumns
})
},
setShowLoader,
'Не удалось загрузить список операций по скважине'
), [idWell])
const updateOperations = () => invokeWebApiWrapperAsync(
async () => {
const skip = ((pageNumAndPageSize.current - 1) * pageNumAndPageSize.pageSize) || 0
const take = pageNumAndPageSize.pageSize
const paginatedOperations = await WellOperationService.getOperations(idWell,
idType, undefined, undefined, undefined, undefined,
const paginatedOperations = await WellOperationService.getOperations(idWell,
idType, undefined, undefined, undefined, undefined,
undefined, undefined, skip, take )
const operations = paginatedOperations?.items ?? []
setOperations(operations)
const total = paginatedOperations.count?? paginatedOperations.items?.length ?? 0
const total = paginatedOperations.count?? paginatedOperations.items?.length ?? 0
setPaginationTotal(total)
},
setShowLoader,
@ -51,78 +85,39 @@ export const WellOperationsEditor = ({idWell, idType}) => {
useEffect(updateOperations, [idWell, idType, pageNumAndPageSize])
const columns = [
makeColumn('Конструкция секции','idWellSectionType', {
editable:true,
input:<SelectFromDictionary dictionary={dictionarySectionType}/>,
width:160,
render:(_, record)=>getByKeyOrReturnKey(dictionarySectionType, record.idWellSectionType)
}),
makeColumn('Операция','idCategory', {
editable:true,
input:<SelectFromDictionary dictionary={dictionaryOperationCategory}/>,
width:200,
render:(_, record)=>getByKeyOrReturnKey(dictionaryOperationCategory, record.idCategory)
}),
makeColumn('Доп. инфо','categoryInfo', {editable:true, width:300, input:<TextArea/>}),
makeColumn('Глубина забоя на начало','depthStart', makeNumericColumnOptions(2, 'depthStart')),
makeColumn('Глубина забоя при завершении','depthEnd', makeNumericColumnOptions(2, 'depthEnd')),
makeColumn('Время начала','dateStart', {
editable:true,
width:200,
input:<DatePickerWrapper/>,
initialValue:moment().format(),
sorter: makeDateSorter('dateStart'),
render:(_, record) =>
<div className={'text-align-r-container'}>
<span>{moment.utc(record.dateStart).local().format(format)}</span>
</div>
}),
makeColumn('Часы','durationHours', makeNumericColumnOptions(2, 'durationHours')),
makeColumn('Комментарий','comment', {editable:true, input:<TextArea/>}),
]
const onAdd = async (operation) => {
operation.idType = idType
operation.wellDepth = +operation.wellDepth
operation.durationHours = +operation.durationHours
await WellOperationService.insertRange(idWell, [operation])
updateOperations()
const handlerProps = {
service: WellOperationService,
setLoader: setShowLoader,
errorMsg: `Не удалось выполнить операцию`,
onComplete: updateOperations,
idWell
}
const onEdit= async (operation) => {
if(!operation.id)
return
operation.idType = idType
operation.wellDepth = +operation.wellDepth
operation.durationHours = +operation.durationHours
await WellOperationService.update(idWell, operation.id, operation)
updateOperations()
}
const recordParser = (record) => ({
...record,
idType,
wellDepth: +record.wellDepth,
durationHours: +record.durationHours
})
const onDelete= async (operation) => {
if(!operation.id)
return
await WellOperationService.delete(idWell, operation.id)
updateOperations()
}
return <LoaderPortal show={showLoader}>
<EditableTable
size='small'
bordered
columns={columns}
dataSource={operations}
onRowAdd={onAdd}
onRowEdit={onEdit}
onRowDelete={onDelete}
pagination={{
current: pageNumAndPageSize.current,
pageSize: pageNumAndPageSize.pageSize,
showSizeChanger: false,
total: paginationTotal,
onChange: (page, pageSize) => setPageNumAndPageSize({current: page, pageSize: pageSize})
}}
return (
<LoaderPortal show={showLoader}>
<EditableTable
size={'small'}
bordered
columns={columns}
dataSource={operations}
onRowAdd={makeActionHandler('insertRange', handlerProps, recordParser)}
onRowEdit={makeActionHandler('update', handlerProps, recordParser)}
onRowDelete={makeActionHandler('delete', handlerProps)}
pagination={{
current: pageNumAndPageSize.current,
pageSize: pageNumAndPageSize.pageSize,
showSizeChanger: false,
total: paginationTotal,
onChange: (page, pageSize) => setPageNumAndPageSize({ current: page, pageSize })
}}
/>
</LoaderPortal>
}
</LoaderPortal>
)
}

View File

@ -1,8 +1,10 @@
import { useState, useEffect } from 'react'
import LoaderPortal from '../../components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { Table, makeColumn, makeColumnsPlanFact } from '../../components/Table'
import { OperationStatService } from '../../services/api'
import { calcDuration } from '../../utils/datetime'
const makeNumberRender = value => {
const v = +value
@ -13,23 +15,15 @@ const makeNumberRender = value => {
const columns = [
makeColumn('Тип секции', 'sectionType'),
makeColumnsPlanFact('Глубина' ,'wellDepth', {render:makeNumberRender}),
makeColumnsPlanFact('Часы' ,'duration', {render:makeNumberRender}),
makeColumnsPlanFact('МСП' ,'rop', {render:makeNumberRender}),
makeColumnsPlanFact('Рейсовая скорость' ,'routeSpeed', {render:makeNumberRender}),
makeColumnsPlanFact('Подъем КНБК' ,'bhaUpSpeed', {render:makeNumberRender}),
makeColumnsPlanFact('Спуск КНБК' ,'bhaDownSpeed', {render:makeNumberRender}),
makeColumnsPlanFact('Спуск ОК' ,'casingDownSpeed', {render:makeNumberRender}),
makeColumnsPlanFact('Глубина, м' ,'wellDepth', { render: makeNumberRender }),
makeColumnsPlanFact('Часы' ,'duration', { render: makeNumberRender }),
makeColumnsPlanFact('МСП, м/ч' ,'rop', { render: makeNumberRender }),
makeColumnsPlanFact('Рейсовая скорость, м/ч','routeSpeed', { render: makeNumberRender }),
makeColumnsPlanFact('Подъем КНБК, м/ч' ,'bhaUpSpeed', { render: makeNumberRender }),
makeColumnsPlanFact('Спуск КНБК, м/ч' ,'bhaDownSpeed', { render: makeNumberRender }),
makeColumnsPlanFact('Спуск ОК, м/ч' ,'casingDownSpeed', { render: makeNumberRender }),
]
const calcDuration = (start, end) => {
const msInDay = 1000 * 60 * 60 * 24
const startD = new Date(start)
const endD = new Date(end)
const ms = endD - startD
return ms / msInDay
}
export const WellSectionsStat = ({idWell}) => {
const [sections, setSections] = useState([])
const [showLoader, setShowLoader] = useState(false)

View File

@ -34,3 +34,34 @@ input.measure-input {
.ml-10px {
margin-left: 10px;
}
.v-div {
display: flex;
height: 180px;
justify-content: center;
}
.v-span {
white-space: pre;
vertical-align: center;
text-align: center;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.measure-column-header {
border: 1px solid lightgrey;
}
.measure-column-value {
border: 1px solid lightgrey;
justify-content: right;
margin-right: 16px;
font-weight: bold;
text-align: right;
padding: 0;
}

View File

@ -1,5 +0,0 @@
export type RawDate = number | string | Date
export function isRawDate(value: unknown): value is RawDate {
return isNaN(new Date(value as RawDate).getTime())
}

View File

@ -1,10 +1,44 @@
export const periodToString = (time?: number) => {
if (!time) return '00:00:00'
const hours = Math.floor(time / 3600)
const minutes = Math.floor(time / 60 - hours * 60)
const seconds = Math.floor(time - hours * 3600 - minutes * 60)
import moment from 'moment'
const toFixed = (num: number) => `${num}`.padStart(2, '0')
export type RawDate = number | string | Date
return `${toFixed(hours)}:${toFixed(minutes)}:${toFixed(seconds)}`
export const defaultFormat: string = 'YYYY.MM.DD HH:mm'
export enum timeInS {
millisecond = 0.001,
second = 1,
minute = second * 60,
hour = minute * 60,
day = hour * 24,
week = day * 7,
}
export function isRawDate(value: unknown): value is RawDate {
return !isNaN(Date.parse(String(value)))
}
export const formatDate = (date: unknown, utc = false, format = defaultFormat) => {
if (!isRawDate(date)) return null
const out = utc ? moment.utc(date).local() : moment(date)
return out.format(format)
}
export const periodToString = (time?: number) => {
if (!time || time <= 0) return '00:00:00'
const days = Math.floor(time / timeInS.day)
time %= timeInS.day
const hours = Math.floor(time / timeInS.hour)
time %= timeInS.hour
const minutes = Math.floor(time / timeInS.minute)
time %= timeInS.minute
const seconds = Math.floor(time / timeInS.second)
const toFixed = (num: number) => String(num).padStart(2, '0')
return `${days > 0 ? days : ''} ${toFixed(hours)}:${toFixed(minutes)}:${toFixed(seconds)}`
}
export const calcDuration = (start: RawDate, end: RawDate) => {
if (!isRawDate(start) || !isRawDate(end)) return
return (+new Date(end) - +new Date(start)) * timeInS.millisecond / timeInS.day
}

View File

@ -1,5 +1,5 @@
export type { RawDate } from './DateTimeUtils'
export { isRawDate } from './DateTimeUtils'
export type { RawDate, timeInS } from './datetime'
export { isRawDate, formatDate, defaultFormat, periodToString } from './datetime'
export const headerHeight = 64