forked from ddrilling/asb_cloud_front
Merge remote-tracking branch 'origin/dev' into DrillingModesPage
This commit is contained in:
commit
f9271b0f46
@ -32,7 +32,7 @@
|
||||
"react_test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"proxy": "http://192.168.1.70:5000",
|
||||
"proxy": "http://192.168.1.58:5000",
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
|
7
src/components/ErrorFetch.js
Normal file
7
src/components/ErrorFetch.js
Normal file
@ -0,0 +1,7 @@
|
||||
export class ErrorFetch extends Error {
|
||||
constructor(status, message) {
|
||||
super(message);
|
||||
this.name = "ErrorFetch"
|
||||
this.status = status
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { makeNumericSorter, makeStringSorter} from './sorters'
|
||||
export { makeDateSorter, makeNumericSorter, makeStringSorter} from './sorters'
|
||||
export { Table } from 'antd'
|
||||
export { EditableTable } from './EditableTable'
|
||||
export { DatePickerWrapper } from './DatePickerWrapper'
|
||||
@ -7,10 +9,28 @@ export { SelectFromDictionary } from './SelectFromDictionary'
|
||||
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
|
||||
export const formatDate='YYYY.MM.DD HH:mm'
|
||||
|
||||
export const numericColumnOptions = {
|
||||
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)){
|
||||
val = !!fixed
|
||||
? (+value).toFixed(fixed)
|
||||
: (+value).toPrecision(5)
|
||||
}
|
||||
|
||||
return (<div className='text-align-r-container'>
|
||||
<span>{val}</span>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export const makeNumericColumnOptions = (fixed?:number, sorterKey?:string ) => ({
|
||||
editable: true,
|
||||
initialValue: 0,
|
||||
width:100,
|
||||
sorter: sorterKey? makeNumericSorter(sorterKey) : null,
|
||||
formItemRules: [
|
||||
{
|
||||
required: true,
|
||||
@ -18,7 +38,8 @@ export const numericColumnOptions = {
|
||||
pattern: RegExpIsFloat,
|
||||
},
|
||||
],
|
||||
};
|
||||
render: makeNumericRender(fixed),
|
||||
})
|
||||
|
||||
/*
|
||||
other - объект с дополнительными свойствами колонки
|
||||
@ -78,41 +99,6 @@ export const makeColumnsPlanFact = (title:string | ReactNode, key:string|string[
|
||||
export const makeFilterTextMatch = (key: string | number) => (filterValue: string | number, dataItem: any) =>
|
||||
dataItem[key] === filterValue
|
||||
|
||||
export const makeNumericSorter = (key: string) => (a: any, b: any) => a[key] - b[key]
|
||||
|
||||
export const makeStringSorter = (key: string) => (a: any, b: any) =>
|
||||
{
|
||||
if(a == null && b == null)
|
||||
return 1
|
||||
|
||||
if(a == null)
|
||||
return 1
|
||||
|
||||
if(b == null)
|
||||
return -1
|
||||
|
||||
let aValue = a[key]
|
||||
let bValue = b[key]
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (isNaN(aValue.charCodeAt(i)) || (aValue.charCodeAt(i) > bValue.charCodeAt(i)))
|
||||
return 1
|
||||
|
||||
if (aValue.charCodeAt(i) > bValue.charCodeAt(i))
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
export const makeDateSorter = (key: string) => (a: any, b: any) => {
|
||||
const date = new Date(a[key])
|
||||
|
||||
if(Number.isNaN(date.getTime()))
|
||||
throw new Error('Date column contains not date formatted string(s)')
|
||||
|
||||
return date.getTime() - new Date(b[key]).getTime()
|
||||
}
|
||||
|
||||
export const makeGroupColumn = (title: string, children: object[]) => ({
|
||||
title: title,
|
||||
children: children,
|
||||
@ -135,17 +121,6 @@ export const makeTextColumn = (
|
||||
...other
|
||||
})
|
||||
|
||||
export const defaultNumericRender = (value: any, row: object) => {
|
||||
const placeholder = '-'
|
||||
if((value === null) ||
|
||||
(value === undefined) ||
|
||||
Number.isNaN(value) ||
|
||||
!Number.isFinite(value))
|
||||
return placeholder
|
||||
|
||||
return (+value).toPrecision(5)
|
||||
}
|
||||
|
||||
export const makeNumericColumn = (title: string, dataIndex: string,
|
||||
filters: object[], filterDelegate: (key: string | number) => any,
|
||||
renderDelegate: (_: any, row: object) => any, width: string, other?: columnPropsOther) => ({
|
||||
@ -156,7 +131,7 @@ export const makeNumericColumn = (title: string, dataIndex: string,
|
||||
onFilter: filterDelegate ? filterDelegate(dataIndex) : null,
|
||||
sorter: makeNumericSorter(dataIndex),
|
||||
width: width,
|
||||
render: renderDelegate??defaultNumericRender,
|
||||
render: renderDelegate??makeNumericRender(),
|
||||
align: 'right',
|
||||
...other
|
||||
})
|
33
src/components/Table/sorters.ts
Normal file
33
src/components/Table/sorters.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export const makeNumericSorter = (key: string) => (a: any, b: any) => a[key] - b[key];
|
||||
|
||||
export const makeStringSorter = (key: string) => (a: any, b: any) => {
|
||||
if (a == null && b == null)
|
||||
return 1;
|
||||
|
||||
if (a == null)
|
||||
return 1;
|
||||
|
||||
if (b == null)
|
||||
return -1;
|
||||
|
||||
let aValue = a[key];
|
||||
let bValue = b[key];
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (isNaN(aValue.charCodeAt(i)) || (aValue.charCodeAt(i) > bValue.charCodeAt(i)))
|
||||
return 1;
|
||||
|
||||
if (aValue.charCodeAt(i) > bValue.charCodeAt(i))
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const makeDateSorter = (key: string) => (a: any, b: any) => {
|
||||
const date = new Date(a[key]);
|
||||
|
||||
if (Number.isNaN(date.getTime()))
|
||||
throw new Error('Date column contains not date formatted string(s)');
|
||||
|
||||
return date.getTime() - new Date(b[key]).getTime();
|
||||
};
|
@ -2,22 +2,38 @@ import { Upload, Button } from 'antd'
|
||||
import { UploadOutlined } from '@ant-design/icons'
|
||||
import { useState } from 'react'
|
||||
import { upload } from './factory'
|
||||
import { ErrorFetch } from './ErrorFetch'
|
||||
|
||||
export default function UploadForm({url, accept, onUploadStart, onUploadComplete, onUploadError}) {
|
||||
export const UploadForm = ({url, accept, style, formData, onUploadStart, onUploadSuccess, onUploadComplete, onUploadError}) => {
|
||||
const [fileList, setfileList] = useState([])
|
||||
|
||||
const handleFileSend = async (values) => {
|
||||
const handleFileSend = async () => {
|
||||
if(onUploadStart)
|
||||
onUploadStart()
|
||||
try {
|
||||
const formData = new FormData()
|
||||
const formDataLocal = new FormData()
|
||||
fileList.forEach((val) => {
|
||||
formData.append("files", val.originFileObj);
|
||||
});
|
||||
await upload(url, formData)
|
||||
formDataLocal.append("files", val.originFileObj)
|
||||
})
|
||||
|
||||
if(formData)
|
||||
for(var propName in formData)
|
||||
formDataLocal.append(propName, formData[propName])
|
||||
|
||||
const response = await upload(url, formDataLocal)
|
||||
if(!response.ok)
|
||||
{
|
||||
const errorText = await response.text()
|
||||
const error = new ErrorFetch(response.status, errorText)
|
||||
throw error
|
||||
}
|
||||
else{
|
||||
if(onUploadSuccess)
|
||||
onUploadSuccess()
|
||||
}
|
||||
} catch(error) {
|
||||
if(onUploadError)
|
||||
onUploadError(error)
|
||||
onUploadError(error)
|
||||
} finally {
|
||||
setfileList([])
|
||||
if(onUploadComplete)
|
||||
@ -27,7 +43,7 @@ export default function UploadForm({url, accept, onUploadStart, onUploadComplete
|
||||
|
||||
const isSendButtonEnabled = fileList.length > 0
|
||||
return(
|
||||
<div style={{display: 'flex'}}>
|
||||
<div style={{display: 'flex', ...style}}>
|
||||
<Upload
|
||||
name ="file"
|
||||
fileList={fileList}
|
||||
|
@ -18,7 +18,7 @@ export const UserView = ({user}) => {
|
||||
<GridItem row={2} col={1}>
|
||||
Фамилия:
|
||||
</GridItem>
|
||||
<GridItem row={3} col={2}>
|
||||
<GridItem row={2} col={2}>
|
||||
{user?.surname}
|
||||
</GridItem>
|
||||
<GridItem row={3} col={1}>
|
||||
|
@ -81,13 +81,14 @@ export const download = async (url:string, fileName?:string) => {
|
||||
}
|
||||
|
||||
export const upload = async (url:string, formData: FormData) => {
|
||||
await fetch(url, {
|
||||
let response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + localStorage['token']
|
||||
},
|
||||
method: 'Post',
|
||||
body: formData,
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
||||
export const downloadFile = async (fileInfo: FileInfoDto) => {
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
Row,
|
||||
Col,
|
||||
Tooltip} from 'antd'
|
||||
import { DataService } from '../services/api'
|
||||
import { TelemetryDataSaubService } from '../services/api'
|
||||
import {generateUUID} from '../services/UidGenerator'
|
||||
import { ArchiveColumn } from '../components/ArchiveColumn'
|
||||
import moment from 'moment'
|
||||
@ -94,7 +94,7 @@ export default function Archive({idWell}) {
|
||||
let startDate = rangeDate[0].toISOString()
|
||||
|
||||
setLoader(true)
|
||||
DataService.getData(idWell, startDate, interval, 2048)
|
||||
TelemetryDataSaubService.getData(idWell, startDate, interval, 2048)
|
||||
.then(handleReceiveDataSaub)
|
||||
.catch(error => {
|
||||
notify(`Не удалось загрузить данные по скважине (${idWell}) c ${rangeDate[0]} по ${rangeDate[1]}`, 'error')
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
makeNumericColumnPlanFact
|
||||
} from "../../components/Table";
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory';
|
||||
import ChartDepthToDay from '../../components/charts/ChartDepthToDay';
|
||||
import ChartTvD from '../WellOperations/ChartTvD';
|
||||
import WellOperationsTable from './WellOperationsTable'
|
||||
import {
|
||||
calcAndUpdateStatsBySections,
|
||||
@ -249,10 +249,10 @@ export default function ClusterSections({ clusterData }) {
|
||||
width={1500}
|
||||
footer={null}
|
||||
>
|
||||
<ChartDepthToDay
|
||||
<ChartTvD
|
||||
dataPlan={tvdDataPlan}
|
||||
dataFact={tvdDataFact}
|
||||
dataForecast={tvdDataForecast} />
|
||||
dataPredict={tvdDataForecast} />
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
|
@ -6,12 +6,11 @@ import {
|
||||
makeTextColumn,
|
||||
makeGroupColumn,
|
||||
makeColumn,
|
||||
makeNumericColumnPlanFact,
|
||||
makeDateSorter
|
||||
} from "../../components/Table";
|
||||
makeDateSorter,
|
||||
makeNumericColumnPlanFact} from "../../components/Table";
|
||||
import { calcAndUpdateStatsBySections, makeFilterMinMaxFunction } from "./functions";
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory';
|
||||
import ChartDepthToDay from '../../components/charts/ChartDepthToDay';
|
||||
import ChartTvD from '../WellOperations/ChartTvD';
|
||||
import WellOperationsTable from './WellOperationsTable'
|
||||
import { getOperations } from "./functions";
|
||||
|
||||
@ -206,10 +205,10 @@ useEffect(() => {
|
||||
width={1500}
|
||||
footer={null}
|
||||
>
|
||||
<ChartDepthToDay
|
||||
<ChartTvD
|
||||
dataPlan={tvdDataPlan}
|
||||
dataFact={tvdDataFact}
|
||||
dataForecast={tvdDataForecast}
|
||||
dataPredict={tvdDataForecast}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
|
@ -6,17 +6,24 @@ const minPrefix = "isMin"
|
||||
export const getOperations = async (idWell) => {
|
||||
const ops = await WellOperationStatService.getTvd(idWell);
|
||||
|
||||
const planData = ops.map(el => {
|
||||
return {key: el.plan?.id, depth: el.plan?.wellDepth, date: el.plan?.startDate}
|
||||
}).filter(el => el.key)
|
||||
const convert = wellOperationDto =>
|
||||
({
|
||||
key: wellOperationDto?.id,
|
||||
depth: wellOperationDto?.depthStart,
|
||||
date: wellOperationDto?.dateStart
|
||||
})
|
||||
|
||||
const factData = ops.map(el => {
|
||||
return {key: el.fact?.id, depth: el.fact?.wellDepth, date: el.fact?.startDate}
|
||||
}).filter(el => el.key)
|
||||
const planData = ops
|
||||
.map(item => convert(item.plan))
|
||||
.filter(el => el.key)
|
||||
|
||||
const predictData = ops.map(el => {
|
||||
return {key: el.predict?.id, depth: el.predict?.wellDepth, date: el.predict?.startDate}
|
||||
}).filter(el => el.key)
|
||||
const factData = ops
|
||||
.map(item => convert(item.fact))
|
||||
.filter(el => el.key)
|
||||
|
||||
const predictData = ops
|
||||
.map(item => convert(item.predict))
|
||||
.filter(el => el.key)
|
||||
|
||||
return { operations: ops, plan: planData, fact: factData, predict: predictData }
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
formatBytes,
|
||||
} from "../../components/factory"
|
||||
import { EditableTable, makePaginationObject } from "../../components/Table"
|
||||
import UploadForm from "../../components/UploadForm"
|
||||
import {UploadForm} from "../../components/UploadForm"
|
||||
import LoaderPortal from "../../components/LoaderPortal"
|
||||
import {UserView} from '../../components/UserView'
|
||||
import {CompanyView} from '../../components/CompanyView'
|
||||
|
@ -1,54 +1,198 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Table, Button, Modal } from 'antd'
|
||||
import { HourglassOutlined } from '@ant-design/icons'
|
||||
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
|
||||
import moment from 'moment'
|
||||
import { CheckSquareOutlined,
|
||||
EditOutlined,
|
||||
SaveOutlined,
|
||||
PlusOutlined,
|
||||
CloseCircleOutlined,
|
||||
DeleteOutlined } from '@ant-design/icons'
|
||||
import { View } from './View'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import { MeasureService } from '../../services/api'
|
||||
import { Editor } from './Editor'
|
||||
import '../../styles/index.css'
|
||||
import '../../styles/measure.css'
|
||||
|
||||
export const MeasureTable = ({idWell, idCategory, title, columns}) => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [showEditor, setShowEditor] = useState(false)
|
||||
const [lastData, setLastData] = useState({})
|
||||
const format='YYYY.MM.DD HH:mm'
|
||||
|
||||
const update = ()=>invokeWebApiWrapperAsync(async()=>{
|
||||
const data = await MeasureService.getLast(idWell, idCategory)
|
||||
setLastData(data)
|
||||
export const MeasureTable = ({idWell, idCategory, title, columns, values, updateMeasuresFunc}) => {
|
||||
|
||||
const [showLoader, setShowLoader] = useState(false);
|
||||
const [displayedValues, setDisplayedValues] = useState({});
|
||||
const [editingColumns, setEditingColumns] = useState(columns);
|
||||
const [isTableEditing, setIsTableEditing] = useState(false);
|
||||
const [editingActionName, setEditingActionName] = useState('');
|
||||
|
||||
const [measuresForm] = Form.useForm();
|
||||
|
||||
const createEditingColumns = (cols, renderDelegate) =>
|
||||
cols.map(col =>
|
||||
({ render: renderDelegate,
|
||||
...col
|
||||
})
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const defaultValuesToDisplay = values[values.length-1]
|
||||
|
||||
setDisplayedValues(defaultValuesToDisplay)
|
||||
}, [values])
|
||||
|
||||
useEffect(() => {
|
||||
let switchableColumns = []
|
||||
|
||||
isTableEditing
|
||||
? switchableColumns = createEditingColumns(columns, () => <Input className='w-100 measure-input' />)
|
||||
: switchableColumns = createEditingColumns(columns, null)
|
||||
|
||||
if(editingActionName === 'edit')
|
||||
measuresForm.setFieldsValue(displayedValues?.data);
|
||||
else if(editingActionName === 'add')
|
||||
measuresForm.resetFields()
|
||||
|
||||
setEditingColumns(switchableColumns)
|
||||
}, [isTableEditing, columns, editingActionName, displayedValues?.data, measuresForm])
|
||||
|
||||
const markMeasuresAsDeleted = async () => {
|
||||
setShowLoader(true)
|
||||
await MeasureService.markAsDelete(idWell, displayedValues.id)
|
||||
updateMeasuresFunc()
|
||||
setShowLoader(false)
|
||||
}
|
||||
, setShowLoader
|
||||
, "не удалось загрузить")
|
||||
|
||||
useEffect(update, [idWell, idCategory])
|
||||
const checkIsDataDefault = () =>
|
||||
displayedValues?.isDefaultData ? true : false
|
||||
|
||||
const timestamp = lastData ? new Date(lastData?.timestamp).toLocaleString() : '-'
|
||||
|
||||
return <LoaderPortal show={showLoader}>
|
||||
<h3>{title}</h3>
|
||||
<span>Дата: {timestamp}</span>
|
||||
|
||||
<Button
|
||||
onClick={() => setShowEditor(true)}
|
||||
icon={<HourglassOutlined/>}>История</Button>
|
||||
<Table
|
||||
style={{marginTop:16}}
|
||||
bordered
|
||||
dataSource = {[lastData?.data]}
|
||||
columns = {columns}
|
||||
scroll={{ x: 400, y: 600 }}/>
|
||||
<Modal
|
||||
title={title}
|
||||
centered
|
||||
visible={showEditor}
|
||||
onOk={() => setShowEditor(false)}
|
||||
onCancel={() => setShowEditor(false)}
|
||||
width="95%"
|
||||
footer={null}
|
||||
const crudButtons =
|
||||
<div className='w-300px mt-8px'>
|
||||
<Button
|
||||
key='add'
|
||||
className='w-33'
|
||||
onClick={() => {
|
||||
setEditingActionName('add')
|
||||
setIsTableEditing(true)
|
||||
}}
|
||||
>
|
||||
<PlusOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
key='edit'
|
||||
className='w-33'
|
||||
onClick={() => {
|
||||
setEditingActionName('edit')
|
||||
setIsTableEditing(true)
|
||||
}}
|
||||
disabled={checkIsDataDefault()}
|
||||
>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="Удалить данные?"
|
||||
onConfirm={() => markMeasuresAsDeleted()}
|
||||
disabled={checkIsDataDefault()}
|
||||
>
|
||||
<Button
|
||||
key='delete'
|
||||
onClick={() => {
|
||||
setEditingActionName('delete')
|
||||
}}
|
||||
disabled={checkIsDataDefault()}
|
||||
>
|
||||
<Editor
|
||||
idWell={idWell}
|
||||
idCategory={idCategory}
|
||||
columns = {columns}
|
||||
onUpdate={update}/>
|
||||
</Modal>
|
||||
</LoaderPortal>
|
||||
<DeleteOutlined style={{margin:'auto 28px'}}/>
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
|
||||
const confirmButtons =
|
||||
<div className='w-300px mt-8px'>
|
||||
<div className='d-flex'>
|
||||
<Button
|
||||
key='confirm'
|
||||
className='w-50'
|
||||
onClick={() => { measuresForm.submit() }}
|
||||
>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
key='decline'
|
||||
className='w-50'
|
||||
onClick={()=> setIsTableEditing(false)}
|
||||
>
|
||||
<CloseCircleOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
let handleSubmitMeasuresForm = async (formData) => {
|
||||
measuresForm.validateFields()
|
||||
|
||||
const measureParams = {
|
||||
idWell: idWell,
|
||||
idCategory: idCategory,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: formData
|
||||
}
|
||||
|
||||
setShowLoader(true)
|
||||
|
||||
if(editingActionName === 'add') {
|
||||
await MeasureService.insert(idWell, measureParams)
|
||||
} else if (editingActionName === 'edit') {
|
||||
measureParams.id = displayedValues.id
|
||||
measureParams.timestamp = displayedValues.timestamp
|
||||
await MeasureService.update(idWell, measureParams)
|
||||
}
|
||||
|
||||
setIsTableEditing(false)
|
||||
updateMeasuresFunc()
|
||||
setShowLoader(false)
|
||||
}
|
||||
|
||||
return <>
|
||||
|
||||
<h2>{title}</h2>
|
||||
|
||||
<div className='d-flex'>
|
||||
<div className='flex-direction-column'>
|
||||
<div className='measure-buttons-container'>
|
||||
{isTableEditing
|
||||
? confirmButtons
|
||||
: crudButtons
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='measure-dates mt-20px'>
|
||||
<Timeline className='mt-12px ml-10px'>
|
||||
{values.map((item, index) =>
|
||||
<Timeline.Item
|
||||
key={index}
|
||||
className='measure-button'
|
||||
onClick={() => setDisplayedValues(item)}
|
||||
dot={item?.id === displayedValues?.id
|
||||
? <CheckSquareOutlined className="timeline-clock-icon" />
|
||||
: null}
|
||||
>
|
||||
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}>
|
||||
{item.timestamp ? moment.utc(item.timestamp).local().format(format) : 'Нет данных'}
|
||||
</span>
|
||||
</Timeline.Item>
|
||||
)}
|
||||
</Timeline>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-100'>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<Form
|
||||
form={measuresForm}
|
||||
onFinish={handleSubmitMeasuresForm}
|
||||
>
|
||||
<View
|
||||
item={displayedValues?.data ?? {}}
|
||||
columns={editingColumns}
|
||||
/>
|
||||
</Form>
|
||||
</LoaderPortal>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
@ -1,48 +1,40 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Button, Modal, Timeline } from 'antd'
|
||||
import moment from 'moment'
|
||||
import { Table, Button, Modal } from 'antd'
|
||||
import { HourglassOutlined } from '@ant-design/icons'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import { MeasureService } from '../../services/api'
|
||||
import { Editor } from './Editor'
|
||||
import TimelineItem from 'antd/lib/timeline/TimelineItem'
|
||||
//import { View } from './View'
|
||||
|
||||
const format='YYYY.MM.DD HH:mm'
|
||||
|
||||
export const MeasureTable2 = ({idWell, idCategory, title, columns}) => {
|
||||
export const MeasureTable = ({idWell, idCategory, title, columns}) => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [showEditor, setShowEditor] = useState(false)
|
||||
const [history, setHistory] = useState([])
|
||||
const [lastData, setLastData] = useState({})
|
||||
|
||||
const update = () => invokeWebApiWrapperAsync(async()=>{
|
||||
const data = await MeasureService.getHisory(idWell, idCategory)
|
||||
const story = data?.map( i=> ({
|
||||
id: i.id,
|
||||
idWell: i.idWell,
|
||||
idCategory: i.idCategory,
|
||||
timestamp: moment.utc(i.timestamp).local().format(format),
|
||||
...i.data}))
|
||||
setHistory(story??[])
|
||||
const update = ()=>invokeWebApiWrapperAsync(async()=>{
|
||||
const data = await MeasureService.getLast(idWell, idCategory)
|
||||
setLastData(data)
|
||||
}
|
||||
, setShowLoader
|
||||
, "не удалось загрузить")
|
||||
|
||||
useEffect(update, [idWell, idCategory])
|
||||
|
||||
const timestamp = lastData ? new Date(lastData?.timestamp).toLocaleString() : '-'
|
||||
|
||||
return <LoaderPortal show={showLoader}>
|
||||
<h3>{title}</h3>
|
||||
<span>Дата: {timestamp}</span>
|
||||
|
||||
<Button
|
||||
onClick={() => setShowEditor(true)}
|
||||
icon={<HourglassOutlined/>}>История</Button>
|
||||
<Timeline>
|
||||
{history.map(item=><TimelineItem value={item}>{item.timestamp}</TimelineItem>)}
|
||||
</Timeline>
|
||||
{/* <View
|
||||
item = {lastData?.data}
|
||||
columns = {columns}/> */}
|
||||
icon={<HourglassOutlined/>}>История</Button>
|
||||
<Table
|
||||
style={{marginTop:16}}
|
||||
bordered
|
||||
dataSource = {[lastData?.data]}
|
||||
columns = {columns}
|
||||
scroll={{ x: 400, y: 600 }}/>
|
||||
<Modal
|
||||
title={title}
|
||||
centered
|
@ -1,38 +1,62 @@
|
||||
import { Empty } from 'antd';
|
||||
import { Empty, Form } from 'antd';
|
||||
import {Grid, GridItem} from '../../components/Grid'
|
||||
import '../../styles/index.css'
|
||||
|
||||
const renderSwitchableColumn = (column, itm) => {
|
||||
if(column.render) {
|
||||
return (
|
||||
<Form.Item
|
||||
key={column.dataIndex}
|
||||
name={column.dataIndex}
|
||||
style={{margin: 0}}
|
||||
//rules={column.formItemRules}
|
||||
>
|
||||
{column.render(itm[column.dataIndex])}
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
|
||||
return <p key={column.title} className='m-5px'>{itm[column.dataIndex]}</p>
|
||||
}
|
||||
|
||||
export const View = ({columns, item}) => {
|
||||
if (!item || !columns?.length)
|
||||
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
|
||||
return <Empty key='empty' image={Empty.PRESENTED_IMAGE_SIMPLE}/>
|
||||
|
||||
const colsCount = 3
|
||||
const viewItems = columns.map( (column, i) => {
|
||||
const row = Math.floor(i / colsCount) + 1
|
||||
const colb = i % colsCount
|
||||
|
||||
return <>
|
||||
<GridItem
|
||||
key={column.dataIndex}
|
||||
row={row}
|
||||
col={colb*2 + 1}
|
||||
background='#00000005'
|
||||
border='1px solid #FFFE'
|
||||
>
|
||||
style={{border:'1px solid lightgrey'}}
|
||||
>
|
||||
{column.title}
|
||||
|
||||
</GridItem>
|
||||
|
||||
<GridItem
|
||||
key={column.title}
|
||||
row={row}
|
||||
col={colb*2 + 2}
|
||||
border='1px solid rgba(0, 0, 0, 0.05)'
|
||||
justifyContect='right'
|
||||
marginRight='16px'
|
||||
fontWeight='bold'
|
||||
textAlign='right'>
|
||||
{column.render ? column.render(item[column.dataIndex]) : item[column.dataIndex]}
|
||||
style={{border:'1px solid lightgrey',
|
||||
justifyContent:'right',
|
||||
marginRight:'16px',
|
||||
fontWeight:'bold',
|
||||
textAlign:'right',
|
||||
padding: 0}}
|
||||
>
|
||||
{renderSwitchableColumn(column, item)}
|
||||
</GridItem>
|
||||
</>
|
||||
})
|
||||
|
||||
return <Grid>
|
||||
{viewItems}
|
||||
</Grid>
|
||||
return <>
|
||||
<Grid>
|
||||
{viewItems}
|
||||
</Grid>
|
||||
</>
|
||||
}
|
@ -42,4 +42,10 @@ export const numericColumnOptions = {
|
||||
export const textColumnOptions = {
|
||||
editable:true,
|
||||
input:<TextArea/>,
|
||||
width:'20rem'}
|
||||
width:'20rem',
|
||||
formItemRules: [
|
||||
{
|
||||
required: true,
|
||||
message: `Введите текст`
|
||||
},
|
||||
],}
|
@ -1,39 +0,0 @@
|
||||
import {makeColumn} from '../../components/Table'
|
||||
import {v, numericColumnOptions, textColumnOptions} from './columnsCommon'
|
||||
|
||||
export const columnsMudDiagram = [
|
||||
makeColumn(v('N пробы'), 'probeNumber', numericColumnOptions),
|
||||
makeColumn(v('Глубина отбора пробы'), 'probeExtractionDepth', numericColumnOptions),
|
||||
{
|
||||
title: 'Литология',
|
||||
key: 'lithology',
|
||||
children: [
|
||||
makeColumn(v('Песчаник (%)'), 'sandstone', numericColumnOptions),
|
||||
makeColumn(v('Алевролит (%)'), 'siltstone', numericColumnOptions),
|
||||
makeColumn(v('Аргиллит (%)'), 'argillit', numericColumnOptions),
|
||||
makeColumn(v('Аргиллит бит. (%)'), 'brokenArgillit', numericColumnOptions),
|
||||
makeColumn(v('Уголь (%)'), 'coal', numericColumnOptions),
|
||||
makeColumn(v('Песок (%)'), 'sand', numericColumnOptions),
|
||||
makeColumn(v('Глина (%)'), 'clay', numericColumnOptions),
|
||||
makeColumn(v('Известняк (%)'), 'camstone', numericColumnOptions),
|
||||
makeColumn(v('Цемент (%)'), 'cement', numericColumnOptions),
|
||||
]
|
||||
},
|
||||
makeColumn('Краткое описание', 'summary', textColumnOptions),
|
||||
makeColumn(v('ЛБА бурового раствора'), 'drillingMud', numericColumnOptions),
|
||||
makeColumn(v('ЛБА (шлама)'), 'sludge', numericColumnOptions),
|
||||
{
|
||||
title: 'Газопоказания',
|
||||
key: 'gasIndications',
|
||||
children: [
|
||||
makeColumn(v('Сумма УВ мах. (абс%)'), 'maxSum', numericColumnOptions),
|
||||
makeColumn(v('С1 метан (отн%)'), 'methane', numericColumnOptions),
|
||||
makeColumn(v('С2 этан (отн%)'), 'ethane', numericColumnOptions),
|
||||
makeColumn(v('С3 пропан (отн%)'), 'propane', numericColumnOptions),
|
||||
makeColumn(v('С4 бутан (отн%)'), 'butane', numericColumnOptions),
|
||||
makeColumn(v('С5 пентан (отн%)'), 'pentane', numericColumnOptions),
|
||||
]
|
||||
},
|
||||
makeColumn(v('Мех. скорость'), 'mechanicalSpeed', numericColumnOptions),
|
||||
makeColumn('Предварительное заключение о насыщении по ГК', 'preliminaryConclusion', textColumnOptions),
|
||||
]
|
@ -31,3 +31,40 @@ export const columnsDrillingFluid = [
|
||||
makeColumn(v("Смазка, %"), "grease",numericColumnOptions),
|
||||
makeColumn(v("Карбонат кальция, кг/м³"), "calciumCarbonate",numericColumnOptions),
|
||||
];
|
||||
|
||||
export const drillingFluidDefaultData = {
|
||||
idWell: 0,
|
||||
key: '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
|
||||
}
|
||||
}
|
@ -1,25 +1,64 @@
|
||||
import { columnsMudDiagram} from './columnsMudDiagram'
|
||||
import { columnsDrillingFluid} from './columnsDrillingFluid'
|
||||
import { columnsNnb } from './columnsNnb'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { columnsMudDiagram} from './mudDiagramData'
|
||||
import { mudDiagramDefaultData} from './mudDiagramData'
|
||||
import { columnsDrillingFluid} from './drillingFluidData'
|
||||
import { drillingFluidDefaultData} from './drillingFluidData'
|
||||
import { columnsNnb } from './nnbData'
|
||||
import { nnbDefaultData } from './nnbData'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import { MeasureService } from '../../services/api'
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { MeasureTable } from './MeasureTable'
|
||||
//import { MeasureTable2 } from './MeasureTable2'
|
||||
|
||||
export default function Measure({idWell}){
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [fluidValues, setFluidValues] = useState([])
|
||||
const [mudValues, setMudValues] = useState([])
|
||||
const [nnbValues, setNnbValues] = useState([])
|
||||
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(false)
|
||||
|
||||
const updateCurrentValues = () => invokeWebApiWrapperAsync(async()=>{
|
||||
const measures = await MeasureService.getHisory(idWell)
|
||||
setIsMeasuresUpdating(false)
|
||||
|
||||
const fluids = measures.filter(el => el.idCategory === 1)
|
||||
setFluidValues(fluids.length ? fluids : [drillingFluidDefaultData])
|
||||
const muds = measures.filter(el => el.idCategory === 2)
|
||||
setMudValues(muds.length ? muds : [mudDiagramDefaultData])
|
||||
const nnbs = measures.filter(el => el.idCategory === 3)
|
||||
setNnbValues(nnbs.length ? nnbs : [nnbDefaultData])
|
||||
}
|
||||
,setShowLoader
|
||||
,`Не удалось загрузить последние данные по скважине ${idWell}`)
|
||||
|
||||
useEffect(updateCurrentValues, [idWell, isMeasuresUpdating])
|
||||
|
||||
return <>
|
||||
<MeasureTable
|
||||
idWell={idWell}
|
||||
idCategory={1}
|
||||
title='Замер бурового раствора'
|
||||
columns={columnsDrillingFluid}/>
|
||||
<MeasureTable
|
||||
idWell={idWell}
|
||||
idCategory={2}
|
||||
title='Шламограмма'
|
||||
columns={columnsMudDiagram}/>
|
||||
<MeasureTable
|
||||
idWell={idWell}
|
||||
idCategory={3}
|
||||
title='ННБ'
|
||||
columns={columnsNnb}/>
|
||||
<LoaderPortal show={showLoader}>
|
||||
<MeasureTable
|
||||
idWell={idWell}
|
||||
idCategory={1}
|
||||
title='Замер бурового раствора'
|
||||
columns={columnsDrillingFluid}
|
||||
values={fluidValues}
|
||||
updateMeasuresFunc = {() => setIsMeasuresUpdating(true)}
|
||||
/>
|
||||
<MeasureTable
|
||||
idWell={idWell}
|
||||
idCategory={2}
|
||||
title='Шламограмма'
|
||||
columns={columnsMudDiagram}
|
||||
values={mudValues}
|
||||
updateMeasuresFunc = {() => setIsMeasuresUpdating(true)}
|
||||
/>
|
||||
<MeasureTable
|
||||
idWell={idWell}
|
||||
idCategory={3}
|
||||
title='ННБ'
|
||||
columns={columnsNnb}
|
||||
values={nnbValues}
|
||||
updateMeasuresFunc = {() => setIsMeasuresUpdating(true)}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
</>
|
||||
}
|
58
src/pages/Measure/mudDiagramData.js
Normal file
58
src/pages/Measure/mudDiagramData.js
Normal file
@ -0,0 +1,58 @@
|
||||
import {makeColumn} from '../../components/Table'
|
||||
import {v, numericColumnOptions, textColumnOptions} from './columnsCommon'
|
||||
|
||||
export const columnsMudDiagram = [
|
||||
makeColumn(v('N пробы'), 'probeNumber', numericColumnOptions),
|
||||
makeColumn(v('Глубина отбора пробы'), 'probeExtractionDepth', numericColumnOptions),
|
||||
makeColumn(v('Песчаник (%)'), 'sandstone', numericColumnOptions),
|
||||
makeColumn(v('Алевролит (%)'), 'siltstone', numericColumnOptions),
|
||||
makeColumn(v('Аргиллит (%)'), 'argillit', numericColumnOptions),
|
||||
makeColumn(v('Аргиллит бит. (%)'), 'brokenArgillit', numericColumnOptions),
|
||||
makeColumn(v('Уголь (%)'), 'coal', numericColumnOptions),
|
||||
makeColumn(v('Песок (%)'), 'sand', numericColumnOptions),
|
||||
makeColumn(v('Глина (%)'), 'clay', numericColumnOptions),
|
||||
makeColumn(v('Известняк (%)'), 'camstone', numericColumnOptions),
|
||||
makeColumn(v('Цемент (%)'), 'cement', numericColumnOptions),
|
||||
makeColumn('Краткое описание', 'summary', textColumnOptions),
|
||||
makeColumn(v('ЛБА бурового раствора'), 'drillingMud', numericColumnOptions),
|
||||
makeColumn(v('ЛБА (шлама)'), 'sludge', numericColumnOptions),
|
||||
makeColumn(v('Сумма УВ мах. (абс%)'), 'maxSum', numericColumnOptions),
|
||||
makeColumn(v('С1 метан (отн%)'), 'methane', numericColumnOptions),
|
||||
makeColumn(v('С2 этан (отн%)'), 'ethane', numericColumnOptions),
|
||||
makeColumn(v('С3 пропан (отн%)'), 'propane', numericColumnOptions),
|
||||
makeColumn(v('С4 бутан (отн%)'), 'butane', numericColumnOptions),
|
||||
makeColumn(v('С5 пентан (отн%)'), 'pentane', numericColumnOptions),
|
||||
makeColumn(v('Мех. скорость'), 'mechanicalSpeed', numericColumnOptions),
|
||||
makeColumn('Предварительное заключение о насыщении по ГК', 'preliminaryConclusion', textColumnOptions),
|
||||
]
|
||||
|
||||
export const mudDiagramDefaultData = {
|
||||
idWell: 0,
|
||||
key: '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": '-'
|
||||
}
|
||||
}
|
@ -19,4 +19,30 @@ export const columnsNnb = [
|
||||
makeColumn(v('Комментарий'), 'comment', numericColumnOptions),
|
||||
makeColumn(v('Разница вертикальных глубин\nмежду планом и фактом'), 'depthPlanFactDifference', numericColumnOptions),
|
||||
makeColumn(v('Расстояние в пространстве\nмежду планом и фактом'), 'distancePlanFactDifference', numericColumnOptions),
|
||||
];
|
||||
];
|
||||
|
||||
export const nnbDefaultData = {
|
||||
idWell: 0,
|
||||
key: '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
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import { Table, makeDateSorter, makeNumericSorter, formatDate} from "../../components/Table"
|
||||
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"
|
||||
|
@ -46,7 +46,7 @@ export default function Well() {
|
||||
<Link to={`${rootPath}/report`}>Рапорт</Link>
|
||||
</PrivateMenuItem>
|
||||
<PrivateMenuItem key="operations" icon={<FolderOutlined />}>
|
||||
<Link to={`${rootPath}/operations/plan`}>Операции по скважине</Link>
|
||||
<Link to={`${rootPath}/operations`}>Операции по скважине</Link>
|
||||
</PrivateMenuItem>
|
||||
<PrivateMenuItem key="archive" icon={<DatabaseOutlined />}>
|
||||
<Link to={`${rootPath}/archive`}>Архив</Link>
|
||||
@ -89,7 +89,7 @@ export default function Well() {
|
||||
<Route path="/well/:idWell/report">
|
||||
<Report idWell={idWell} />
|
||||
</Route>
|
||||
<Route path="/well/:idWell/operations/:tab">
|
||||
<Route path="/well/:idWell/operations">
|
||||
<WellOperations idWell={idWell} />
|
||||
</Route>
|
||||
<Route path="/well/:idWell/archive">
|
||||
|
@ -97,7 +97,7 @@ const makeDataset = (data, label, color, width=1.5, dash) => ({
|
||||
borderDash: dash,
|
||||
})
|
||||
|
||||
export default function ChartDepthToDay({dataPlan, dataFact, dataForecast}) {
|
||||
export default function ChartTvD({dataPlan, dataFact, dataPredict}) {
|
||||
const chartRef = useRef(null)
|
||||
const [chart, setChart] = useState()
|
||||
|
||||
@ -105,7 +105,7 @@ export default function ChartDepthToDay({dataPlan, dataFact, dataForecast}) {
|
||||
let data = {
|
||||
datasets: [
|
||||
makeDataset(dataFact, 'Факт', '#0A0'),
|
||||
makeDataset(dataForecast, 'Прогноз', 'purple', 1, [7,3]),
|
||||
makeDataset(dataPredict, 'Прогноз', 'purple', 1, [7,3]),
|
||||
makeDataset(dataPlan, 'План', '#C004', 4),
|
||||
]
|
||||
}
|
||||
@ -127,7 +127,7 @@ export default function ChartDepthToDay({dataPlan, dataFact, dataForecast}) {
|
||||
chart.data = data
|
||||
chart.update()
|
||||
}
|
||||
}, [chart, dataPlan, dataFact, dataForecast])
|
||||
}, [chart, dataPlan, dataFact, dataPredict])
|
||||
|
||||
return (<canvas ref={chartRef} />)
|
||||
}
|
54
src/pages/WellOperations/ImportExportBar.jsx
Normal file
54
src/pages/WellOperations/ImportExportBar.jsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { Button, Tooltip, Modal } from "antd";
|
||||
import {useState} from 'react'
|
||||
import { FileOutlined, ImportOutlined, ExportOutlined } from '@ant-design/icons'
|
||||
import { download } from '../../components/factory'
|
||||
import { ImportOperations } from './ImportOperations'
|
||||
|
||||
const style = {margin:4}
|
||||
|
||||
export const ImportExportBar = ({idWell, onImported}) =>{
|
||||
const [isImportModalVisible, setIsImportModalVisible] = useState(false)
|
||||
|
||||
const downloadTemplate = async () => download(`/api/well/${idWell}/wellOperations/tamplate`)
|
||||
const downloadExport = async () => download(`/api/well/${idWell}/wellOperations/export`)
|
||||
|
||||
const onDone = () => {
|
||||
setIsImportModalVisible(false)
|
||||
if(onImported)
|
||||
onImported()
|
||||
}
|
||||
|
||||
return <>
|
||||
<Tooltip title = "Импорт - загрузить файл с операциями на сервер">
|
||||
<Button
|
||||
icon={<ImportOutlined/>}
|
||||
style={style}
|
||||
onClick={_=>setIsImportModalVisible(true)}/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title = "Экспорт - скачать файл с операциями по скважине">
|
||||
<Button
|
||||
icon={<ExportOutlined/>}
|
||||
style={style}
|
||||
onClick={downloadExport}/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title = "Скачать шаблон">
|
||||
<Button
|
||||
icon={<FileOutlined/>}
|
||||
style={style}
|
||||
onClick={downloadTemplate}/>
|
||||
</Tooltip>
|
||||
|
||||
<Modal
|
||||
title='Импорт'
|
||||
visible={isImportModalVisible}
|
||||
onCancel={_=>setIsImportModalVisible(false)}
|
||||
|
||||
footer={null}>
|
||||
<ImportOperations
|
||||
idWell={idWell}
|
||||
onDone={onDone}/>
|
||||
</Modal >
|
||||
</>
|
||||
}
|
41
src/pages/WellOperations/ImportOperations.jsx
Normal file
41
src/pages/WellOperations/ImportOperations.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import {Switch} from 'antd'
|
||||
import {useState} from 'react'
|
||||
import { ErrorFetch } from '../../components/ErrorFetch'
|
||||
import {UploadForm} from '../../components/UploadForm'
|
||||
|
||||
export const ImportOperations = ({idWell, onDone}) =>{
|
||||
const [deleteBeforeImport, setDeleteBeforeImport] = useState(false)
|
||||
const [errorText, setErrorText] = useState('')
|
||||
|
||||
const url = `/api/well/${idWell}/wellOperations/import`
|
||||
|
||||
const onUploadSuccess = () => {
|
||||
setErrorText('')
|
||||
if(onDone)
|
||||
onDone()
|
||||
}
|
||||
|
||||
const onUploadError = (error) => {
|
||||
if(error instanceof ErrorFetch && error.status === 400)
|
||||
setErrorText(`Не удалось импортировать.\n ${error?.message}`)
|
||||
else
|
||||
setErrorText(`Не удалось импортировать.`)
|
||||
}
|
||||
|
||||
const getUrl = () => deleteBeforeImport
|
||||
? url + '/1'
|
||||
: url + '/0'
|
||||
|
||||
return <div>
|
||||
<p>Загрузить файл excel с операциями на сервер</p>
|
||||
<span>Очистить список операций перед импортом </span>
|
||||
<Switch onChange={setDeleteBeforeImport} checked={deleteBeforeImport}/>
|
||||
<UploadForm
|
||||
url={getUrl()}
|
||||
style={{marginTop:'24px'}}
|
||||
onUploadSuccess={onUploadSuccess}
|
||||
onUploadError={onUploadError}
|
||||
/>
|
||||
<span style={{color:'red', fontWeight:'bold'}}>{errorText}</span>
|
||||
</div>
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import ChartDepthToDay from '../../components/charts/ChartDepthToDay';
|
||||
import ChartTvD from './ChartTvD';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory';
|
||||
import { getOperations } from '../Cluster/functions';
|
||||
@ -7,18 +7,15 @@ import { getOperations } from '../Cluster/functions';
|
||||
export const Tvd = ({ idWell }) => {
|
||||
const [dataPlan, setDataPlan] = useState([]);
|
||||
const [dataFact, setDataFact] = useState([]);
|
||||
const [dataForecast, setDataForecast] = useState([]);
|
||||
const [dataPredict, setDataPredict] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const operations = await getOperations(idWell);
|
||||
|
||||
setDataPlan(operations.plan)
|
||||
|
||||
setDataFact(operations.fact)
|
||||
|
||||
setDataForecast(operations.predict)
|
||||
setDataPredict(operations.predict)
|
||||
},
|
||||
null,
|
||||
`Не удалось загрузить операции по скважине "${idWell}"`,
|
||||
@ -29,10 +26,10 @@ export const Tvd = ({ idWell }) => {
|
||||
<div className="container">
|
||||
<div>
|
||||
<h2 className={'mt-20px'}>График Глубина-день</h2>
|
||||
<ChartDepthToDay
|
||||
<ChartTvD
|
||||
dataPlan={dataPlan}
|
||||
dataFact={dataFact}
|
||||
dataForecast={dataForecast} />
|
||||
dataPredict={dataPredict} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -6,10 +6,8 @@ import {
|
||||
DatePickerWrapper,
|
||||
SelectFromDictionary,
|
||||
makeColumn,
|
||||
numericColumnOptions,
|
||||
makeNumericSorter,
|
||||
makeDateSorter,
|
||||
defaultNumericRender } from "../../components/Table"
|
||||
makeNumericColumnOptions} from "../../components/Table"
|
||||
import LoaderPortal from '../../components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||
import { WellOperationService} from '../../services/api'
|
||||
@ -20,20 +18,6 @@ const { TextArea } = Input;
|
||||
const basePageSize = 160;
|
||||
const format='YYYY.MM.DD HH:mm'
|
||||
|
||||
const numericSortColumnOptions = {
|
||||
...numericColumnOptions,
|
||||
sorter: makeNumericSorter('wellDepth'),
|
||||
render:(value) =>
|
||||
<div className='text-align-r-container'>
|
||||
<span>{value}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
const durationFormattedColumnOptions = {
|
||||
...numericColumnOptions,
|
||||
render: defaultNumericRender
|
||||
}
|
||||
|
||||
export const WellOperationsEditor = ({idWell, idType}) => {
|
||||
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({current:1, pageSize:basePageSize})
|
||||
const [paginationTotal, setPaginationTotal] = useState(0)
|
||||
@ -81,19 +65,20 @@ export const WellOperationsEditor = ({idWell, idType}) => {
|
||||
render:(_, record)=>getByKeyOrReturnKey(dictionaryOperationCategory, record.idCategory)
|
||||
}),
|
||||
makeColumn('Доп. инфо','categoryInfo', {editable:true, width:300, input:<TextArea/>}),
|
||||
makeColumn('Глубина забоя','wellDepth', numericSortColumnOptions),
|
||||
makeColumn('Время начала','startDate', {
|
||||
makeColumn('Глубина забоя на начало','depthStart', makeNumericColumnOptions(2, 'depthStart')),
|
||||
makeColumn('Глубина забоя при завершении','depthEnd', makeNumericColumnOptions(2, 'depthEnd')),
|
||||
makeColumn('Время начала','dateStart', {
|
||||
editable:true,
|
||||
width:200,
|
||||
input:<DatePickerWrapper/>,
|
||||
initialValue:moment().format(),
|
||||
sorter: makeDateSorter('startDate'),
|
||||
sorter: makeDateSorter('dateStart'),
|
||||
render:(_, record) =>
|
||||
<div className={'text-align-r-container'}>
|
||||
<span>{moment.utc(record.startDate).local().format(format)}</span>
|
||||
<span>{moment.utc(record.dateStart).local().format(format)}</span>
|
||||
</div>
|
||||
}),
|
||||
makeColumn('Часы','durationHours', durationFormattedColumnOptions),
|
||||
makeColumn('Часы','durationHours', makeNumericColumnOptions(2, 'durationHours')),
|
||||
makeColumn('Комментарий','comment', {editable:true, input:<TextArea/>}),
|
||||
]
|
||||
|
||||
|
@ -1,17 +1,22 @@
|
||||
import {Layout, Menu} from "antd";
|
||||
import {Switch, Link, Route, Redirect, useParams} from "react-router-dom";
|
||||
import {Switch, Link, Route, Redirect, useParams, useHistory} from "react-router-dom";
|
||||
import { FolderOutlined } from "@ant-design/icons";
|
||||
import { WellOperationsEditor } from './WellOperationsEditor'
|
||||
import { WellSectionsStat } from './WellSectionsStat'
|
||||
import { Tvd } from './Tvd'
|
||||
import { WellOpeationsModes } from './WellOpeationsModes'
|
||||
import { ImportExportBar } from "./ImportExportBar";
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
export default function WellOperations({idWell}) {
|
||||
let {tab} = useParams()
|
||||
let history = useHistory()
|
||||
const rootPath = `/well/${idWell}/operations`;
|
||||
|
||||
const onImported = () => {
|
||||
history.push(`${rootPath}`)
|
||||
}
|
||||
|
||||
return(<>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
@ -33,6 +38,7 @@ export default function WellOperations({idWell}) {
|
||||
<Menu.Item key={'modes'} icon={<FolderOutlined />}>
|
||||
<Link to={`${rootPath}/modes`}>Режимы</Link>
|
||||
</Menu.Item>
|
||||
<ImportExportBar idWell={idWell} onImported={onImported}/>
|
||||
</Menu>
|
||||
<Layout>
|
||||
<Content className="site-layout-background">
|
||||
@ -53,7 +59,7 @@ export default function WellOperations({idWell}) {
|
||||
<WellOpeationsModes idWell={idWell}/>
|
||||
</Route>
|
||||
<Route path={rootPath}>
|
||||
<Redirect to={`${rootPath}/tvd`}/>
|
||||
<Redirect to={`${rootPath}/plan`}/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Content>
|
||||
|
@ -20,7 +20,7 @@ export type DrillParamsDto = {
|
||||
topDriveSpeedMin?: number;
|
||||
topDriveSpeedAvg?: number;
|
||||
topDriveSpeedMax?: number;
|
||||
flowMin?: number;
|
||||
flowAvg?: number;
|
||||
flowMax?: number;
|
||||
consumptionMin?: number;
|
||||
consumptionAvg?: number;
|
||||
consumptionMax?: number;
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ export class AdminTelemetryService {
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static async mergeTelemetries(
|
||||
requestBody?: Array<number>,
|
||||
): Promise<any> {
|
||||
requestBody?: Array<number>,
|
||||
): Promise<any> {
|
||||
const result = await __request({
|
||||
method: 'POST',
|
||||
path: `/merge`,
|
||||
|
@ -15,10 +15,10 @@ export class DrillParamsService {
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static async getDefaultDrillParams(
|
||||
idWell?: number,
|
||||
startDepth?: number,
|
||||
endDepth?: number,
|
||||
): Promise<DrillParamsDto> {
|
||||
idWell?: number,
|
||||
startDepth?: number,
|
||||
endDepth?: number,
|
||||
): Promise<DrillParamsDto> {
|
||||
const result = await __request({
|
||||
method: 'GET',
|
||||
path: `/drillParams/idWell/autoParams`,
|
||||
@ -39,9 +39,9 @@ export class DrillParamsService {
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static async saveDrillParams(
|
||||
idWell?: number,
|
||||
requestBody?: DrillParamsDto,
|
||||
): Promise<number> {
|
||||
idWell?: number,
|
||||
requestBody?: DrillParamsDto,
|
||||
): Promise<number> {
|
||||
const result = await __request({
|
||||
method: 'POST',
|
||||
path: `/drillParams/idWell`,
|
||||
|
@ -149,15 +149,15 @@ export class WellOperationService {
|
||||
* Импортирует операции из excel (xlsx) файла
|
||||
* @param idWell id скважины
|
||||
* @param options Удалить операции перед импортом = 1, если фал валидный
|
||||
* @param requestBody
|
||||
* @param requestBody
|
||||
* @returns any Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static async import(
|
||||
idWell: number,
|
||||
options: number,
|
||||
requestBody?: any,
|
||||
): Promise<any> {
|
||||
idWell: number,
|
||||
options: number,
|
||||
requestBody?: any,
|
||||
): Promise<any> {
|
||||
const result = await __request({
|
||||
method: 'POST',
|
||||
path: `/api/well/${idWell}/wellOperations/import/${options}`,
|
||||
@ -173,8 +173,8 @@ export class WellOperationService {
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static async export(
|
||||
idWell: number,
|
||||
): Promise<string> {
|
||||
idWell: number,
|
||||
): Promise<string> {
|
||||
const result = await __request({
|
||||
method: 'GET',
|
||||
path: `/api/well/${idWell}/wellOperations/export`,
|
||||
@ -184,13 +184,13 @@ export class WellOperationService {
|
||||
|
||||
/**
|
||||
* Возвращает шаблон файла импорта
|
||||
* @param idWell
|
||||
* @param idWell
|
||||
* @returns string Success
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static async getTamplate(
|
||||
idWell: string,
|
||||
): Promise<string> {
|
||||
idWell: string,
|
||||
): Promise<string> {
|
||||
const result = await __request({
|
||||
method: 'GET',
|
||||
path: `/api/well/${idWell}/wellOperations/tamplate`,
|
||||
|
@ -11,6 +11,10 @@ body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-direction-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.d-inline {
|
||||
display: inline;
|
||||
}
|
||||
@ -19,10 +23,30 @@ body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.w-15 {
|
||||
width: 15%
|
||||
}
|
||||
|
||||
.w-33 {
|
||||
width: 33%
|
||||
}
|
||||
|
||||
.w-50 {
|
||||
width: 50%
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.m-0 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mt-8px {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mt-20px {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@ -35,6 +59,10 @@ body {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.ml-10px {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.ml-30px {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
36
src/styles/measure.css
Normal file
36
src/styles/measure.css
Normal file
@ -0,0 +1,36 @@
|
||||
input.measure-input {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.w-300px {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.measure-buttons-container {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.measure-dates {
|
||||
height: 160px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.measure-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-timeline {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.m-5px {
|
||||
margin: 5px 5px;
|
||||
}
|
||||
|
||||
.mt-12px {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.ml-10px {
|
||||
margin-left: 10px;
|
||||
}
|
Loading…
Reference in New Issue
Block a user