forked from ddrilling/asb_cloud_front
Merge branch 'dev' into feature/CF2-72-AdminPanel
This commit is contained in:
commit
8552a40506
@ -1,6 +1,6 @@
|
|||||||
import { DatePicker } from 'antd'
|
import { DatePicker } from 'antd'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { formatDate } from './index'
|
import { defaultFormat } from '../../utils'
|
||||||
|
|
||||||
export type DatePickerWrapperProps = {
|
export type DatePickerWrapperProps = {
|
||||||
value: moment.Moment,
|
value: moment.Moment,
|
||||||
@ -13,7 +13,7 @@ export const DatePickerWrapper: React.FC<DatePickerWrapperProps> = ({value, onCh
|
|||||||
allowClear={false}
|
allowClear={false}
|
||||||
defaultValue={moment()}
|
defaultValue={moment()}
|
||||||
value={moment.utc(value).local()}
|
value={moment.utc(value).local()}
|
||||||
format={formatDate}
|
format={defaultFormat}
|
||||||
showTime
|
showTime
|
||||||
onChange={(date) => onChange(date)}
|
onChange={(date) => onChange(date)}
|
||||||
{...other}
|
{...other}
|
||||||
|
@ -6,17 +6,25 @@ import { invokeWebApiWrapperAsync } from '../factory'
|
|||||||
|
|
||||||
const newRowKeyValue = 'newRow'
|
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(
|
(record) => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
const addIdWell = (...params) => idWell ? [idWell, ...params] : params
|
||||||
|
if (typeof recordParser === 'function')
|
||||||
|
record = recordParser(record)
|
||||||
|
|
||||||
if (action === 'insert') {
|
if (action === 'insert') {
|
||||||
record.key = Date.now()
|
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) {
|
} else if (record.id) {
|
||||||
if (action === 'update')
|
if (action === 'update')
|
||||||
await service.put(record.id, record)
|
await service.put(...addIdWell(record.id, record))
|
||||||
else if (action === 'delete')
|
else if (action === 'delete')
|
||||||
await service.delete(record.id)
|
await service.delete(...addIdWell(record.id))
|
||||||
}
|
}
|
||||||
if (onComplete)
|
if (onComplete)
|
||||||
await onComplete()
|
await onComplete()
|
||||||
|
@ -10,7 +10,6 @@ export { DatePickerWrapper } from './DatePickerWrapper'
|
|||||||
export { SelectFromDictionary } from './SelectFromDictionary'
|
export { SelectFromDictionary } from './SelectFromDictionary'
|
||||||
|
|
||||||
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
|
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
|
||||||
export const formatDate = 'YYYY.MM.DD HH:mm'
|
|
||||||
|
|
||||||
export const makeNumericRender = (fixed?: number) => (value: any, row: object): ReactNode => {
|
export const makeNumericRender = (fixed?: number) => (value: any, row: object): ReactNode => {
|
||||||
let val = '-'
|
let val = '-'
|
||||||
@ -209,7 +208,7 @@ export const makeSelectColumn = <T extends unknown = string>(
|
|||||||
other?: columnPropsOther
|
other?: columnPropsOther
|
||||||
) => makeColumn(title, dataIndex, {
|
) => makeColumn(title, dataIndex, {
|
||||||
input: <Select options={options}/>,
|
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
|
...other
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,14 +2,17 @@ import { TreeSelect } from 'antd'
|
|||||||
import { DefaultValueType } from 'rc-tree-select/lib/interface'
|
import { DefaultValueType } from 'rc-tree-select/lib/interface'
|
||||||
import { useState, useEffect, ReactNode } from 'react'
|
import { useState, useEffect, ReactNode } from 'react'
|
||||||
import { useHistory, useRouteMatch } from 'react-router-dom'
|
import { useHistory, useRouteMatch } from 'react-router-dom'
|
||||||
import LoaderPortal from './LoaderPortal'
|
|
||||||
import { DepositService } from '../services/api'
|
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 DepositIcon } from '../images/DepositIcon.svg'
|
||||||
import { ReactComponent as ClusterIcon } from '../images/ClusterIcon.svg'
|
import { ReactComponent as ClusterIcon } from '../images/ClusterIcon.svg'
|
||||||
import { DepositDto } from '../services/api'
|
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'
|
import '../styles/wellTreeSelect.css'
|
||||||
|
|
||||||
export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown'
|
export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Dispatch, SetStateAction } from "react"
|
import { notification } from 'antd'
|
||||||
import { notification } from 'antd';
|
import { Dispatch, SetStateAction } from 'react'
|
||||||
import { FileInfoDto } from '../services/api'
|
import { FileInfoDto } from '../services/api'
|
||||||
|
|
||||||
const notificationTypeDictionary = new Map([
|
const notificationTypeDictionary = new Map([
|
||||||
@ -15,29 +15,28 @@ const notificationTypeDictionary = new Map([
|
|||||||
* @param notifyType для параметра типа. Допустимые значение 'error', 'warning', 'info'
|
* @param notifyType для параметра типа. Допустимые значение 'error', 'warning', 'info'
|
||||||
*/
|
*/
|
||||||
export const notify = (body: string | any, notifyType: string = 'info', other?: any) => {
|
export const notify = (body: string | any, notifyType: string = 'info', other?: any) => {
|
||||||
if(!body)
|
if (!body) return
|
||||||
return
|
|
||||||
const instance = notificationTypeDictionary.get(notifyType) ??
|
const instance = notificationTypeDictionary.get(notifyType) ??
|
||||||
notificationTypeDictionary.get('open')
|
notificationTypeDictionary.get('open')
|
||||||
|
|
||||||
instance?.notifyInstance({
|
instance?.notifyInstance({
|
||||||
description: body,
|
description: body,
|
||||||
message: instance.caption,
|
message: instance.caption,
|
||||||
placement: "bottomRight",
|
placement: 'bottomRight',
|
||||||
duration: 10,
|
duration: 10,
|
||||||
...other
|
...other
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type asyncFunction = (...args:any) => Promise<any|void>;
|
type asyncFunction = (...args: any) => Promise<any|void>
|
||||||
|
|
||||||
export const invokeWebApiWrapperAsync = async (
|
export const invokeWebApiWrapperAsync = async (
|
||||||
funcAsync: asyncFunction,
|
funcAsync: asyncFunction,
|
||||||
setShowLoader: Dispatch<SetStateAction<boolean>>,
|
setShowLoader?: Dispatch<SetStateAction<boolean>>,
|
||||||
errorNotifyText: (string | ((ex: unknown) => string))
|
errorNotifyText?: (string | ((ex: unknown) => string))
|
||||||
) => {
|
) => {
|
||||||
if(setShowLoader)
|
setShowLoader?.(true)
|
||||||
setShowLoader(true)
|
|
||||||
try{
|
try{
|
||||||
await funcAsync()
|
await funcAsync()
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@ -49,48 +48,42 @@ export const invokeWebApiWrapperAsync = async (
|
|||||||
else notify(errorNotifyText, 'error')
|
else notify(errorNotifyText, 'error')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if(setShowLoader)
|
setShowLoader?.(false)
|
||||||
setShowLoader(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const download = async (url: string, fileName?: string) => {
|
export const download = async (url: string, fileName?: string) => {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer ' + localStorage['token']
|
Authorization: `Bearer ${localStorage['token']}`
|
||||||
},
|
},
|
||||||
method: 'Get'
|
method: 'Get'
|
||||||
})
|
})
|
||||||
if(response.status !== 200)
|
if(response.status !== 200)
|
||||||
throw response
|
throw response
|
||||||
const requestFileName = decodeURI(fileName
|
const requestFileName = decodeURI(
|
||||||
??response.headers
|
fileName
|
||||||
.get('content-disposition')
|
?? response.headers.get('content-disposition')?.split(';').pop()?.split(`'`).pop()
|
||||||
?.split(';')
|
?? url.replace('\\','/').split('/').pop()
|
||||||
.splice(-1)[0]
|
?? 'file'
|
||||||
.split("'")
|
)
|
||||||
.splice(-1)[0]
|
|
||||||
?? url.replace('\\','/')
|
|
||||||
.split('/')
|
|
||||||
.splice(-1)[0]
|
|
||||||
?? 'file')
|
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const reader = new FileReader();
|
const reader = new FileReader()
|
||||||
reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob)
|
||||||
reader.onload = function (e) {
|
reader.onload = (e) => {
|
||||||
let a = document.createElement('a');
|
const a = document.createElement('a')
|
||||||
a.href = (e.target?.result?.toString() ?? '');
|
a.href = (e.target?.result?.toString() ?? '')
|
||||||
a.download = requestFileName;
|
a.download = requestFileName
|
||||||
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
|
document.body.appendChild(a) // we need to append the element to the dom -> otherwise it will not work in firefox
|
||||||
a.click();
|
a.click()
|
||||||
a.remove();
|
a.remove()
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const upload = async (url: string, formData: FormData) => {
|
export const upload = async (url: string, formData: FormData) => {
|
||||||
let response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer ' + localStorage['token']
|
Authorization: `Bearer ${localStorage['token']}`
|
||||||
},
|
},
|
||||||
method: 'Post',
|
method: 'Post',
|
||||||
body: formData,
|
body: formData,
|
||||||
@ -115,20 +108,3 @@ export const formatBytes = (bytes:number) => {
|
|||||||
else
|
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
|
|
||||||
}
|
|
@ -55,6 +55,13 @@ export default function ClusterWells({statsWells}) {
|
|||||||
if (!filtersWellsType.some((el) => el.text === well.wellType))
|
if (!filtersWellsType.some((el) => el.text === well.wellType))
|
||||||
filtersWellsType.push({ text: well.wellType, value: 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 {
|
return {
|
||||||
key: well.caption,
|
key: well.caption,
|
||||||
id: well.id,
|
id: well.id,
|
||||||
@ -62,8 +69,8 @@ export default function ClusterWells({statsWells}) {
|
|||||||
wellType: well.wellType,
|
wellType: well.wellType,
|
||||||
factStart: well.total?.fact?.start,
|
factStart: well.total?.fact?.start,
|
||||||
factEnd: well.total?.fact?.end,
|
factEnd: well.total?.fact?.end,
|
||||||
periodPlan: (new Date(well.total?.plan?.end) - new Date(well.total?.plan?.start)) / DAY_IN_MS,
|
periodPlan: periodPlanValue,
|
||||||
periodFact: (new Date(well.total?.fact?.end) - new Date(well.total?.fact?.start)) / DAY_IN_MS,
|
periodFact: periodFactValue,
|
||||||
rateOfPenetrationPlan: well.total?.plan?.rop,
|
rateOfPenetrationPlan: well.total?.plan?.rop,
|
||||||
rateOfPenetrationFact: well.total?.fact?.rop,
|
rateOfPenetrationFact: well.total?.fact?.rop,
|
||||||
routeSpeedPlan: well.total?.plan?.routeSpeed,
|
routeSpeedPlan: well.total?.plan?.routeSpeed,
|
||||||
@ -91,7 +98,9 @@ export default function ClusterWells({statsWells}) {
|
|||||||
setTableData(data)
|
setTableData(data)
|
||||||
}, [statsWells])
|
}, [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 = [
|
const columns = [
|
||||||
makeTextColumn('скв №', 'caption', null, null,
|
makeTextColumn('скв №', 'caption', null, null,
|
||||||
@ -110,11 +119,11 @@ export default function ClusterWells({statsWells}) {
|
|||||||
makeTextColumn('Тип скв.', 'wellType', filtersWellsType, null, (text) => text ?? '-'),
|
makeTextColumn('Тип скв.', 'wellType', filtersWellsType, null, (text) => text ?? '-'),
|
||||||
makeGroupColumn('Фактические сроки', [
|
makeGroupColumn('Фактические сроки', [
|
||||||
makeColumn('начало', 'factStart', { sorter: makeDateSorter('factStart'), render: getDate }),
|
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('Продолжительность, сут', 'period', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('МСП', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('МСП, м/ч', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('Рейсовая скорость', 'routeSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('Рейсовая скорость, м/ч', 'routeSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('НПВ, сут', 'notProductiveTime', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('НПВ, сут', 'notProductiveTime', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
{
|
{
|
||||||
title: 'TVD',
|
title: 'TVD',
|
||||||
|
@ -1,29 +1,38 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { Modal } from 'antd'
|
import { Modal } from 'antd'
|
||||||
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { Table } from '../../components/Table'
|
import { Table } from '../../components/Table'
|
||||||
import { formatDate } from './MeasureTable'
|
import { formatDate } from '../../utils'
|
||||||
|
|
||||||
import { v } from './columnsCommon'
|
import { v } from './columnsCommon'
|
||||||
|
|
||||||
export const InclinometryTable = React.memo(({ group, visible, onClose }) => {
|
const tableScroll = { y: 400, x: 1300 }
|
||||||
const [tableColumns, setTableColumns] = useState([])
|
|
||||||
const [tableData, setTableData] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const dateColumn = {
|
||||||
setTableColumns([{
|
|
||||||
title: 'Дата',
|
title: 'Дата',
|
||||||
key: 'date',
|
key: 'date',
|
||||||
dataIndex: 'date',
|
dataIndex: 'date',
|
||||||
render: (item) => formatDate(item),
|
render: (date) => formatDate(date, true) ?? 'Нет данных',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
width: '8rem',
|
width: '8rem',
|
||||||
},
|
}
|
||||||
...(group?.columns?.map((column) => ({...column, title: v(column.title)})) ?? [])
|
|
||||||
])
|
|
||||||
}, [group?.columns])
|
|
||||||
|
|
||||||
useEffect(() => {
|
export const InclinometryTable = memo(({ group, visible, onClose }) => {
|
||||||
setTableData(group?.values?.map(row => ({ date: row.timestamp, ...row.data })))
|
const [tableColumns, setTableColumns] = useState([])
|
||||||
}, [group?.values])
|
const [tableData, setTableData] = useState([])
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
return !group?.columns ? null : (
|
return !group?.columns ? null : (
|
||||||
<Modal
|
<Modal
|
||||||
@ -37,14 +46,9 @@ export const InclinometryTable = React.memo(({ group, visible, onClose }) => {
|
|||||||
<Table
|
<Table
|
||||||
dataSource={tableData}
|
dataSource={tableData}
|
||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
scroll={{
|
scroll={tableScroll}
|
||||||
y: 400,
|
|
||||||
x: 1300
|
|
||||||
}}
|
|
||||||
bordered
|
bordered
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default InclinometryTable
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
|
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
|
||||||
import moment from 'moment'
|
|
||||||
import {
|
import {
|
||||||
CheckSquareOutlined,
|
CheckSquareOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
@ -9,15 +8,19 @@ import {
|
|||||||
CloseCircleOutlined,
|
CloseCircleOutlined,
|
||||||
DeleteOutlined
|
DeleteOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { View } from './View'
|
|
||||||
import LoaderPortal from '../../components/LoaderPortal'
|
import LoaderPortal from '../../components/LoaderPortal'
|
||||||
import { MeasureService } from '../../services/api'
|
import { MeasureService } from '../../services/api'
|
||||||
import '../../styles/index.css'
|
|
||||||
import '../../styles/measure.css'
|
|
||||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||||
|
|
||||||
const dateFormat = 'YYYY.MM.DD HH:mm'
|
import { View } from './View'
|
||||||
export const formatDate = (date) => date ? moment.utc(date).local().format(dateFormat) : 'Нет данных'
|
|
||||||
|
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}) => {
|
export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButtons}) => {
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -29,11 +32,10 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
|
|||||||
|
|
||||||
const [measuresForm] = Form.useForm()
|
const [measuresForm] = Form.useForm()
|
||||||
|
|
||||||
const createEditingColumns = (cols, renderDelegate) =>
|
|
||||||
cols.map(col => ({ render: renderDelegate, ...col }))
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = [group.defaultValue].concat(group.values ?? [])
|
let data = [group.defaultValue]
|
||||||
|
if (group?.values?.length > 0)
|
||||||
|
data = group.values
|
||||||
setData(data)
|
setData(data)
|
||||||
setDisplayedValues(data.at(-1))
|
setDisplayedValues(data.at(-1))
|
||||||
}, [group.defaultValue, group.values])
|
}, [group.defaultValue, group.values])
|
||||||
@ -136,12 +138,12 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
|
|||||||
key={index}
|
key={index}
|
||||||
className={'measure-button'}
|
className={'measure-button'}
|
||||||
onClick={() => setDisplayedValues(item)}
|
onClick={() => setDisplayedValues(item)}
|
||||||
dot={item?.id !== displayedValues?.id ? null :
|
dot={item?.id === displayedValues?.id &&
|
||||||
<CheckSquareOutlined className={'timeline-clock-icon'} />
|
<CheckSquareOutlined className={'timeline-clock-icon'} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}>
|
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : undefined}>
|
||||||
{formatDate(item.timestamp)}
|
{formatDate(item.timestamp, true) ?? 'Нет данных'}
|
||||||
</span>
|
</span>
|
||||||
</Timeline.Item>
|
</Timeline.Item>
|
||||||
)}
|
)}
|
||||||
|
@ -1,36 +1,24 @@
|
|||||||
import React from 'react'
|
import { memo, Fragment } from 'react'
|
||||||
import { Empty, Form } from 'antd'
|
import { Empty, Form } from 'antd'
|
||||||
|
|
||||||
import { Grid, GridItem } from '../../components/Grid'
|
import { Grid, GridItem } from '../../components/Grid'
|
||||||
|
|
||||||
import '../../styles/index.css'
|
import '../../styles/index.css'
|
||||||
|
import '../../styles/measure.css'
|
||||||
|
|
||||||
const colsCount = 3
|
const colsCount = 3
|
||||||
|
|
||||||
const headerCellStyle = {
|
export const View = memo(({ columns, item }) => !item || !columns?.length ? (
|
||||||
border:'1px solid lightgrey'
|
<Empty key={'empty'} image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
}
|
) : (
|
||||||
|
|
||||||
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>
|
<Grid>
|
||||||
{columns.map((column, i) => (
|
{columns.map((column, i) => (
|
||||||
<>
|
<Fragment key={i}>
|
||||||
<GridItem
|
<GridItem
|
||||||
key={column.dataIndex}
|
key={column.dataIndex}
|
||||||
row={Math.floor(i / colsCount) + 1}
|
row={Math.floor(i / colsCount) + 1}
|
||||||
col={(i % colsCount) * 2 + 1}
|
col={(i % colsCount) * 2 + 1}
|
||||||
style={headerCellStyle}
|
className={'measure-column-header'}
|
||||||
>
|
>
|
||||||
{column.title}
|
{column.title}
|
||||||
</GridItem>
|
</GridItem>
|
||||||
@ -39,23 +27,24 @@ export const View = React.memo(({columns, item}) => {
|
|||||||
key={column.title}
|
key={column.title}
|
||||||
row={Math.floor(i / colsCount) + 1}
|
row={Math.floor(i / colsCount) + 1}
|
||||||
col={(i % colsCount) * 2 + 2}
|
col={(i % colsCount) * 2 + 2}
|
||||||
style={valueCellStyle}
|
className={'measure-column-value'}
|
||||||
|
style={{ padding: 0 }}
|
||||||
>
|
>
|
||||||
{column.render ? (
|
{column.render ? (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
key={column.dataIndex}
|
key={column.dataIndex}
|
||||||
name={column.dataIndex}
|
name={column.dataIndex}
|
||||||
style={{ margin: 0 }}
|
style={{ margin: 0 }}
|
||||||
//rules={column.formItemRules}
|
|
||||||
>
|
>
|
||||||
{column.render(item[column.dataIndex])}
|
{column.render(item[column.dataIndex])}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : (
|
) : (
|
||||||
<p key={column.title} className='m-5px'>{item[column.dataIndex]}</p>
|
<p key={column.title} className={'m-5px'}>
|
||||||
|
{item[column.dataIndex]}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
))
|
||||||
})
|
|
||||||
|
@ -1,27 +1,12 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Input } from 'antd'
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import { RegExpIsFloat } from '../../components/Table'
|
import { RegExpIsFloat } from '../../components/Table'
|
||||||
|
|
||||||
const { TextArea } = Input
|
import '../../styles/measure.css'
|
||||||
|
|
||||||
export const v = (text) => (
|
export const v = (text) => (
|
||||||
<div style={{
|
<div className={'v-div'}>
|
||||||
display: 'flex',
|
<span className={'v-span'}>
|
||||||
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)',
|
|
||||||
}}>
|
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -42,7 +27,7 @@ export const numericColumnOptions = {
|
|||||||
|
|
||||||
export const textColumnOptions = {
|
export const textColumnOptions = {
|
||||||
editable: true,
|
editable: true,
|
||||||
input: <TextArea/>,
|
input: <Input.TextArea/>,
|
||||||
width: '3rem',
|
width: '3rem',
|
||||||
formItemRules: [{
|
formItemRules: [{
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { makeColumn } from '../../components/Table';
|
import { makeColumn } from '../../components/Table'
|
||||||
|
|
||||||
import { numericColumnOptions, textColumnOptions } from './columnsCommon'
|
import { numericColumnOptions, textColumnOptions } from './columnsCommon'
|
||||||
|
|
||||||
export const columnsDrillingFluid = [
|
export const columnsDrillingFluid = [
|
||||||
@ -30,7 +31,7 @@ export const columnsDrillingFluid = [
|
|||||||
makeColumn('Твердая фаза раствора, %', 'fluidSolidPhase', numericColumnOptions),
|
makeColumn('Твердая фаза раствора, %', 'fluidSolidPhase', numericColumnOptions),
|
||||||
makeColumn('Смазка, %', 'grease', numericColumnOptions),
|
makeColumn('Смазка, %', 'grease', numericColumnOptions),
|
||||||
makeColumn('Карбонат кальция, кг/м³', 'calciumCarbonate', numericColumnOptions),
|
makeColumn('Карбонат кальция, кг/м³', 'calciumCarbonate', numericColumnOptions),
|
||||||
];
|
]
|
||||||
|
|
||||||
export const drillingFluidDefaultData = {
|
export const drillingFluidDefaultData = {
|
||||||
idWell: 0,
|
idWell: 0,
|
||||||
@ -38,33 +39,33 @@ export const drillingFluidDefaultData = {
|
|||||||
idCategory: 0,
|
idCategory: 0,
|
||||||
isDefaultData: true,
|
isDefaultData: true,
|
||||||
data: {
|
data: {
|
||||||
'name': 0,
|
name: 0,
|
||||||
'temperature': 0,
|
temperature: 0,
|
||||||
'density': 0,
|
density: 0,
|
||||||
'conditionalViscosity': 0,
|
conditionalViscosity: 0,
|
||||||
'r300': 0,
|
r300: 0,
|
||||||
'r600': 0,
|
r600: 0,
|
||||||
'r3r6': 0,
|
r3r6: 0,
|
||||||
'dnsDpa': 0,
|
dnsDpa: 0,
|
||||||
'plasticViscocity': 0,
|
plasticViscocity: 0,
|
||||||
'snsDpa': 0,
|
snsDpa: 0,
|
||||||
'r3r649С': 0,
|
r3r649С: 0,
|
||||||
'dns49Cdpa': 0,
|
dns49Cdpa: 0,
|
||||||
'plasticViscocity49c': 0,
|
plasticViscocity49c: 0,
|
||||||
'sns49Cdpa': 0,
|
sns49Cdpa: 0,
|
||||||
'mbt': 0,
|
mbt: 0,
|
||||||
'sand': 0,
|
sand: 0,
|
||||||
'filtering': 0,
|
filtering: 0,
|
||||||
'crust': 0,
|
crust: 0,
|
||||||
'ktk': 0,
|
ktk: 0,
|
||||||
'ph': 0,
|
ph: 0,
|
||||||
'hardness': 0,
|
hardness: 0,
|
||||||
'chlorides': 0,
|
chlorides: 0,
|
||||||
'pf': 0,
|
pf: 0,
|
||||||
'mf': 0,
|
mf: 0,
|
||||||
'pm': 0,
|
pm: 0,
|
||||||
'fluidSolidPhase': 0,
|
fluidSolidPhase: 0,
|
||||||
'grease': 0,
|
grease: 0,
|
||||||
'calciumCarbonate': 0
|
calciumCarbonate: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,55 @@
|
|||||||
|
import { Button } from 'antd'
|
||||||
import { useState, useEffect } from 'react'
|
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 { columnsNnb, nnbDefaultData } from './nnbData'
|
||||||
import { columnsMudDiagram, mudDiagramDefaultData } from './mudDiagramData'
|
import { columnsMudDiagram, mudDiagramDefaultData } from './mudDiagramData'
|
||||||
import { columnsDrillingFluid, drillingFluidDefaultData } from './drillingFluidData'
|
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 = [
|
const defaultData = [
|
||||||
{
|
{
|
||||||
idCategory: 1,
|
idCategory: 1,
|
||||||
title: 'Замер бурового раствора',
|
title: 'Замер бурового раствора',
|
||||||
columns: columnsDrillingFluid,
|
columns: columnsDrillingFluid,
|
||||||
values: [drillingFluidDefaultData],
|
|
||||||
defaultValue: drillingFluidDefaultData,
|
defaultValue: drillingFluidDefaultData,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
idCategory: 2,
|
idCategory: 2,
|
||||||
title: 'Шламограмма',
|
title: 'Шламограмма',
|
||||||
columns: columnsMudDiagram,
|
columns: columnsMudDiagram,
|
||||||
values: [mudDiagramDefaultData],
|
|
||||||
defaultValue: mudDiagramDefaultData,
|
defaultValue: mudDiagramDefaultData,
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
idCategory: 3,
|
idCategory: 3,
|
||||||
title: 'ННБ',
|
title: 'ННБ',
|
||||||
columns: columnsNnb,
|
columns: columnsNnb,
|
||||||
values: [nnbDefaultData],
|
|
||||||
defaultValue: 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 [showLoader, setShowLoader] = useState(false)
|
||||||
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(false)
|
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(true)
|
||||||
const [data, setData] = useState(defaultData)
|
const [data, setData] = useState(defaultData)
|
||||||
const [tableIdx, setTableIdx] = useState(-1)
|
const [tableIdx, setTableIdx] = useState(-1)
|
||||||
|
|
||||||
const updateCurrentValues = () => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
if (!isMeasuresUpdating) return
|
||||||
const measures = await MeasureService.getHisory(idWell)
|
const measures = await MeasureService.getHisory(idWell)
|
||||||
setIsMeasuresUpdating(false)
|
setIsMeasuresUpdating(false)
|
||||||
|
|
||||||
@ -57,28 +65,9 @@ export default function Measure({idWell}){
|
|||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить последние данные по скважине ${idWell}`
|
`Не удалось загрузить последние данные по скважине ${idWell}`
|
||||||
)
|
), [idWell, isMeasuresUpdating])
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
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 <>
|
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
{data.map((group, idx) => (
|
{data.map((group, idx) => (
|
||||||
<MeasureTable
|
<MeasureTable
|
||||||
@ -86,7 +75,7 @@ export default function Measure({idWell}){
|
|||||||
idWell={idWell}
|
idWell={idWell}
|
||||||
group={group}
|
group={group}
|
||||||
updateMeasuresFunc={() => setIsMeasuresUpdating(true)}
|
updateMeasuresFunc={() => setIsMeasuresUpdating(true)}
|
||||||
additionalButtons={group.additionalButtons?.(group, idx)}
|
additionalButtons={group.additionalButtons?.(group, idx, setTableIdx)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<InclinometryTable
|
<InclinometryTable
|
||||||
@ -95,5 +84,7 @@ export default function Measure({idWell}){
|
|||||||
group={data[tableIdx]}
|
group={data[tableIdx]}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
</>
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Measure
|
||||||
|
@ -32,27 +32,27 @@ export const mudDiagramDefaultData = {
|
|||||||
idCategory: 0,
|
idCategory: 0,
|
||||||
isDefaultData: true,
|
isDefaultData: true,
|
||||||
data: {
|
data: {
|
||||||
'probeNumber': 0,
|
probeNumber: 0,
|
||||||
'probeExtractionDepth': 0,
|
probeExtractionDepth: 0,
|
||||||
'sandstone': 0,
|
sandstone: 0,
|
||||||
'siltstone': 0,
|
siltstone: 0,
|
||||||
'argillit': 0,
|
argillit: 0,
|
||||||
'brokenArgillit': 0,
|
brokenArgillit: 0,
|
||||||
'coal': 0,
|
coal: 0,
|
||||||
'sand': 0,
|
sand: 0,
|
||||||
'clay': 0,
|
clay: 0,
|
||||||
'camstone': 0,
|
camstone: 0,
|
||||||
'cement': 0,
|
cement: 0,
|
||||||
'summary': '-',
|
summary: '-',
|
||||||
'drillingMud': 0,
|
drillingMud: 0,
|
||||||
'sludge': 0,
|
sludge: 0,
|
||||||
'maxSum': 0,
|
maxSum: 0,
|
||||||
'methane': 0,
|
methane: 0,
|
||||||
'ethane': 0,
|
ethane: 0,
|
||||||
'propane': 0,
|
propane: 0,
|
||||||
'butane': 0,
|
butane: 0,
|
||||||
'pentane': 0,
|
pentane: 0,
|
||||||
'mechanicalSpeed': 0,
|
mechanicalSpeed: 0,
|
||||||
'preliminaryConclusion': '-'
|
preliminaryConclusion: '-'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ export const columnsNnb = [
|
|||||||
makeColumn('Комментарий', 'comment', numericColumnOptions),
|
makeColumn('Комментарий', 'comment', numericColumnOptions),
|
||||||
makeColumn('Разница вертикальных глубин\nмежду планом и фактом', 'depthPlanFactDifference', numericColumnOptions),
|
makeColumn('Разница вертикальных глубин\nмежду планом и фактом', 'depthPlanFactDifference', numericColumnOptions),
|
||||||
makeColumn('Расстояние в пространстве\nмежду планом и фактом', 'distancePlanFactDifference', numericColumnOptions),
|
makeColumn('Расстояние в пространстве\nмежду планом и фактом', 'distancePlanFactDifference', numericColumnOptions),
|
||||||
];
|
]
|
||||||
|
|
||||||
export const nnbDefaultData = {
|
export const nnbDefaultData = {
|
||||||
idWell: 0,
|
idWell: 0,
|
||||||
@ -27,22 +27,22 @@ export const nnbDefaultData = {
|
|||||||
idCategory: 0,
|
idCategory: 0,
|
||||||
isDefaultData: true,
|
isDefaultData: true,
|
||||||
data: {
|
data: {
|
||||||
'depth': 0,
|
depth: 0,
|
||||||
'zenithAngle': 0,
|
zenithAngle: 0,
|
||||||
'magneticAzimuth': 0,
|
magneticAzimuth: 0,
|
||||||
'trueAzimuth': 0,
|
trueAzimuth: 0,
|
||||||
'directAzimuth': 0,
|
directAzimuth: 0,
|
||||||
'verticalDepth': 0,
|
verticalDepth: 0,
|
||||||
'absoluteMark': 0,
|
absoluteMark: 0,
|
||||||
'localNorthOffset': 0,
|
localNorthOffset: 0,
|
||||||
'localEastOffset': 0,
|
localEastOffset: 0,
|
||||||
'outFallOffset': 0,
|
outFallOffset: 0,
|
||||||
'offsetAzimuth': 0,
|
offsetAzimuth: 0,
|
||||||
'areaIntensity': '-',
|
areaIntensity: '-',
|
||||||
'offsetStopAngle': 0,
|
offsetStopAngle: 0,
|
||||||
'zenithIntensity': 0,
|
zenithIntensity: 0,
|
||||||
'comment': '-',
|
comment: '-',
|
||||||
'depthPlanFactDifference': 0,
|
depthPlanFactDifference: 0,
|
||||||
'distancePlanFactDifference': 0
|
distancePlanFactDifference: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,57 @@
|
|||||||
import { useState, useEffect } from "react"
|
import { Button, Tooltip } from 'antd'
|
||||||
import { Table, formatDate, makeDateSorter, makeNumericSorter} from "../../components/Table"
|
import { useState, useEffect } from 'react'
|
||||||
import { Button, Tooltip } from "antd"
|
|
||||||
import { FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
|
import { FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||||
import { ReportService } from "../../services/api"
|
|
||||||
import { invokeWebApiWrapperAsync, downloadFile, formatTimespan} from "../../components/factory"
|
import { ReportService } from '../../services/api'
|
||||||
import LoaderPortal from "../../components/LoaderPortal"
|
import { formatDate, periodToString } from '../../utils'
|
||||||
import moment from "moment"
|
import LoaderPortal from '../../components/LoaderPortal'
|
||||||
|
import { Table, makeDateSorter, makeNumericSorter } from '../../components/Table'
|
||||||
|
import { invokeWebApiWrapperAsync, downloadFile } from '../../components/factory'
|
||||||
|
|
||||||
const imgPaths = {
|
const imgPaths = {
|
||||||
".pdf": <FilePdfOutlined/>,
|
'.pdf': <FilePdfOutlined/>,
|
||||||
".las": <FileTextOutlined />,
|
'.las': <FileTextOutlined/>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Название",
|
title: 'Название',
|
||||||
dataIndex: "name",
|
dataIndex: 'name',
|
||||||
key: "name",
|
key: 'name',
|
||||||
render: (name, report) => (
|
render: (name, report) => (
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type={'link'}
|
||||||
icon={imgPaths[report.format]}
|
icon={imgPaths[report.format]}
|
||||||
onClick={(_) => downloadFile(report.file)}
|
onClick={() => downloadFile(report.file)}
|
||||||
download={name}
|
download={name}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
},
|
}, {
|
||||||
{
|
title: <Tooltip title={'Дата формирования'}>Сформирован</Tooltip>,
|
||||||
title: <Tooltip title="Дата формирования">Сформирован</Tooltip>,
|
dataIndex: 'date',
|
||||||
dataIndex: "date",
|
key: 'date',
|
||||||
key: "date",
|
|
||||||
sorter: makeDateSorter('date'),
|
sorter: makeDateSorter('date'),
|
||||||
render: date=>moment(date).format(formatDate),
|
render: (date) => formatDate(date),
|
||||||
},
|
}, {
|
||||||
{
|
title: <Tooltip title={'Дата начала периода рапорта'}>С</Tooltip>,
|
||||||
title: <Tooltip title="Дата начала периода рапорта">С</Tooltip>,
|
dataIndex: 'begin',
|
||||||
dataIndex: "begin",
|
key: 'begin',
|
||||||
key: "begin",
|
|
||||||
sorter: makeDateSorter('begin'),
|
sorter: makeDateSorter('begin'),
|
||||||
render: date=>moment(date).format(formatDate),
|
render: (date) => formatDate(date),
|
||||||
},
|
}, {
|
||||||
{
|
title: <Tooltip title={'Дата окончания периода рапорта'}>По</Tooltip>,
|
||||||
title: <Tooltip title="Дата окончания периода рапорта">По</Tooltip>,
|
dataIndex: 'end',
|
||||||
dataIndex: "end",
|
key: 'end',
|
||||||
key: "end",
|
|
||||||
sorter: makeDateSorter('end'),
|
sorter: makeDateSorter('end'),
|
||||||
render: date=>moment(date).format(formatDate),
|
render: (date) => formatDate(date),
|
||||||
},
|
}, {
|
||||||
{
|
title: <Tooltip title={'шаг сетки графиков'}>шаг, сек</Tooltip>,
|
||||||
title: <Tooltip title="шаг сетки графиков">шаг, сек</Tooltip>,
|
dataIndex: 'step',
|
||||||
dataIndex: "step",
|
key: 'step',
|
||||||
key: "step",
|
|
||||||
|
|
||||||
sorter: makeNumericSorter('step'),
|
sorter: makeNumericSorter('step'),
|
||||||
|
render: step => periodToString(step),
|
||||||
render: step => formatTimespan(step)
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -64,18 +59,25 @@ export const Reports = ({idWell}) => {
|
|||||||
const [reports, setReports] = useState([])
|
const [reports, setReports] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
useEffect(()=>invokeWebApiWrapperAsync(async()=>{
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
|
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
|
||||||
const reports = reportsResponse.map(r => ({ ...r, key: r.id ?? r.name ?? r.date }))
|
const reports = reportsResponse.map(r => ({ ...r, key: r.id ?? r.name ?? r.date }))
|
||||||
setReports(reports)
|
setReports(reports)
|
||||||
},setShowLoader), [idWell])
|
},
|
||||||
|
setShowLoader,
|
||||||
|
`Не удалось загрузить список рапортов по скважине "${idWell}"`
|
||||||
|
), [idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<Table
|
<Table
|
||||||
size='small' bordered
|
size={'small'}
|
||||||
|
bordered
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={reports}
|
dataSource={reports}
|
||||||
pagination={{pageSize:13}}/>
|
pagination={{ pageSize: 13 }}
|
||||||
</LoaderPortal>)
|
/>
|
||||||
|
</LoaderPortal>
|
||||||
|
)
|
||||||
}
|
}
|
@ -1,63 +1,55 @@
|
|||||||
import { useState, useEffect } from "react"
|
import 'moment/locale/ru'
|
||||||
import { DatePicker, Radio, Button, Select, notification } from "antd"
|
import moment from 'moment'
|
||||||
import "moment/locale/ru"
|
import { useState, useEffect } from 'react'
|
||||||
import moment from "moment"
|
import { DatePicker, Radio, Button, Select, notification } from 'antd'
|
||||||
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"
|
|
||||||
|
|
||||||
const { RangePicker } = DatePicker
|
import { ReportService } from '../../services/api'
|
||||||
const { Option } = Select
|
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 = [
|
const timePeriodNames = [
|
||||||
{ label: "1 секунда", value: 1 },
|
{ label: '1 секунда', value: 1 },
|
||||||
{ label: "10 секунд", value: 10 },
|
{ label: '10 секунд', value: 10 },
|
||||||
{ label: "1 минута", value: 60 },
|
{ label: '1 минута', value: 60 },
|
||||||
{ label: "5 минут", value: 300 },
|
{ label: '5 минут', value: 300 },
|
||||||
{ label: "30 минут", value: 1800 },
|
{ label: '30 минут', value: 1800 },
|
||||||
{ label: "1 час", value: 3600 },
|
{ label: '1 час', value: 3600 },
|
||||||
{ label: "6 часов", value: 21600 },
|
{ label: '6 часов', value: 21600 },
|
||||||
{ label: "12 часов", value: 43200 },
|
{ label: '12 часов', value: 43200 },
|
||||||
{ label: "1 день", value: 86400 },
|
{ label: '1 день', value: 86400 },
|
||||||
{ label: "1 неделя", value: 604800 },
|
{ 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 }) {
|
export default function Report({ idWell }) {
|
||||||
const [aviableDateRange, setAviableDateRange] = useState([
|
const [aviableDateRange, setAviableDateRange] = useState([moment(), moment()])
|
||||||
moment(),
|
|
||||||
moment(),
|
|
||||||
])
|
|
||||||
const [filterDateRange, setFilterDateRange] = useState([
|
const [filterDateRange, setFilterDateRange] = useState([
|
||||||
moment().subtract(1, "days").startOf("day"),
|
moment().subtract(1, 'days').startOf('day'),
|
||||||
moment().startOf("day"),
|
moment().startOf('day'),
|
||||||
])
|
])
|
||||||
const [step, setStep] = useState(timePeriodNames[2].value)
|
const [step, setStep] = useState(timePeriodNames[2].value)
|
||||||
const [format, setFormat] = useState(0)
|
const [format, setFormat] = useState(0)
|
||||||
const [pagesCount, setPagesCount] = useState(0)
|
const [pagesCount, setPagesCount] = useState(0)
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const periodOptions = timePeriodNames.map((item) => (
|
const handleReportCreation = async () => await invokeWebApiWrapperAsync(
|
||||||
<Option key={item.value} value={item.value}>
|
async () => {
|
||||||
{item.label}
|
|
||||||
</Option>
|
|
||||||
))
|
|
||||||
|
|
||||||
const handleReportCreation = async () => {
|
|
||||||
let begin = filterDateRange[0].toISOString()
|
|
||||||
let end = filterDateRange[1].toISOString()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const taskId = await ReportService.createReport(
|
const taskId = await ReportService.createReport(
|
||||||
idWell,
|
idWell,
|
||||||
step,
|
step,
|
||||||
format,
|
format,
|
||||||
begin,
|
filterDateRange[0].toISOString(),
|
||||||
end
|
filterDateRange[1].toISOString()
|
||||||
)
|
)
|
||||||
if (!taskId) return
|
if (!taskId) return
|
||||||
|
|
||||||
@ -65,12 +57,8 @@ export default function Report({ idWell }) {
|
|||||||
if (progressData) {
|
if (progressData) {
|
||||||
notification.open({
|
notification.open({
|
||||||
key: taskId,
|
key: taskId,
|
||||||
message: "Создание отчета:",
|
message: 'Создание отчета:',
|
||||||
description: (
|
description: <ReportCreationNotify progressData={progressData} />,
|
||||||
<ReportCreationNotify
|
|
||||||
progressData={progressData}
|
|
||||||
></ReportCreationNotify>
|
|
||||||
),
|
|
||||||
duration: 0,
|
duration: 0,
|
||||||
onClose: unSubscribeReportHub,
|
onClose: unSubscribeReportHub,
|
||||||
})
|
})
|
||||||
@ -81,64 +69,52 @@ export default function Report({ idWell }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const unSubscribeReportHub = Subscribe(
|
const unSubscribeReportHub = Subscribe(
|
||||||
"hubs/reports",
|
'hubs/reports',
|
||||||
"GetReportProgress",
|
'GetReportProgress',
|
||||||
`Report_${taskId}`,
|
`Report_${taskId}`,
|
||||||
handleReportProgress
|
handleReportProgress
|
||||||
)
|
)
|
||||||
} catch (error) {
|
},
|
||||||
notify(
|
setShowLoader,
|
||||||
`Не удалось создать отчет по скважине (${idWell}) c
|
`Не удалось создать отчет по скважине (${idWell}) c
|
||||||
${filterDateRange[0].format("DD.MM.YYYY hh:mm:ss")} по
|
${filterDateRange[0].format(dateTimeFormat)} по
|
||||||
${filterDateRange[1].format("DD.MM.YYYY hh:mm:ss")}`,
|
${filterDateRange[1].format(dateTimeFormat)}`
|
||||||
"error"
|
|
||||||
)
|
)
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disabledDate = (current) => {
|
const disabledDate = (current) => !current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
|
||||||
return current < aviableDateRange[0] || current > aviableDateRange[1]
|
|
||||||
}
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const datesRangeResponse = await ReportService.getReportsDateRange(idWell)
|
||||||
|
if (!datesRangeResponse?.from || !datesRangeResponse.to)
|
||||||
|
throw new Error('Формат ответа неверный!')
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
invokeWebApiWrapperAsync(async () => {
|
|
||||||
const datesRangeResponse = await ReportService.getReportsDateRange(
|
|
||||||
idWell
|
|
||||||
)
|
|
||||||
const datesRange = [
|
const datesRange = [
|
||||||
moment(datesRangeResponse.from),
|
moment(datesRangeResponse.from),
|
||||||
moment(datesRangeResponse.to),
|
moment(datesRangeResponse.to),
|
||||||
]
|
]
|
||||||
setAviableDateRange(datesRange)
|
setAviableDateRange(datesRange)
|
||||||
|
|
||||||
let from = moment(datesRangeResponse.to)
|
const from = moment.max(moment(datesRange[1]).subtract(1, 'days'), datesRange[0])
|
||||||
from = from.subtract(1, "days")
|
|
||||||
if (from < datesRange[0]) from = datesRange[0]
|
|
||||||
|
|
||||||
const filterDateDefaults = [
|
setFilterDateRange([
|
||||||
from.startOf("day"),
|
from.startOf('day'),
|
||||||
moment(datesRangeResponse.to).startOf("day"),
|
moment(datesRange[1]).startOf('day'),
|
||||||
]
|
])
|
||||||
setFilterDateRange(filterDateDefaults)
|
},
|
||||||
}, setShowLoader),
|
setShowLoader,
|
||||||
[idWell]
|
`Не удалось получить диапозон дат рапортов для скважины "${idWell}"`
|
||||||
)
|
), [idWell])
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
() =>
|
|
||||||
invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
async () => {
|
||||||
if (!filterDateRange || filterDateRange.length < 2) return
|
if (filterDateRange?.length !== 2) return
|
||||||
const begin = filterDateRange[0].toISOString()
|
|
||||||
const end = filterDateRange[1].toISOString()
|
|
||||||
const pagesCount = await ReportService.getReportSize(
|
const pagesCount = await ReportService.getReportSize(
|
||||||
idWell,
|
idWell,
|
||||||
step,
|
step,
|
||||||
format,
|
format,
|
||||||
begin,
|
filterDateRange[0].toISOString(),
|
||||||
end
|
filterDateRange[1].toISOString()
|
||||||
)
|
)
|
||||||
setPagesCount(pagesCount)
|
setPagesCount(pagesCount)
|
||||||
},
|
},
|
||||||
@ -146,17 +122,15 @@ export default function Report({ idWell }) {
|
|||||||
`Не удалось получить предварительные параметры отчета c
|
`Не удалось получить предварительные параметры отчета c
|
||||||
${filterDateRange[0].format(dateTimeFormat)} по
|
${filterDateRange[0].format(dateTimeFormat)} по
|
||||||
${filterDateRange[1].format(dateTimeFormat)}`
|
${filterDateRange[1].format(dateTimeFormat)}`
|
||||||
),
|
), [filterDateRange, step, format, idWell])
|
||||||
[filterDateRange, step, format, idWell]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LoaderPortal show={showLoader}>
|
<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>Диапазон дат отчета</div>
|
<div>Диапазон дат отчета</div>
|
||||||
<RangePicker
|
<DatePicker.RangePicker
|
||||||
disabledDate={disabledDate}
|
disabledDate={disabledDate}
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
onCalendarChange={setFilterDateRange}
|
onCalendarChange={setFilterDateRange}
|
||||||
@ -164,38 +138,28 @@ export default function Report({ idWell }) {
|
|||||||
value={filterDateRange}
|
value={filterDateRange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-30px">
|
<div className={'ml-30px'}>
|
||||||
<div>Шаг графиков</div>
|
<div>Шаг графиков</div>
|
||||||
<Select style={{ width: "8rem" }}
|
<Select
|
||||||
onChange={e=>{
|
style={{ width: '8rem' }}
|
||||||
setStep(e)
|
onChange={e => setStep(e)}
|
||||||
}}
|
value={step}
|
||||||
value={step}>
|
options={timePeriodNames}
|
||||||
{periodOptions}
|
/>
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-30px">
|
<div className={'ml-30px'}>
|
||||||
<div>Формат отчета</div>
|
<div>Формат отчета</div>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
onChange={(e) => setFormat(e.target.value)}
|
onChange={(e) => setFormat(e.target.value)}
|
||||||
|
options={reportFormats}
|
||||||
value={format}
|
value={format}
|
||||||
>
|
/>
|
||||||
<Radio.Button value={0}>PDF</Radio.Button>
|
|
||||||
<Radio.Button value={1}>LAS</Radio.Button>
|
|
||||||
</Radio.Group>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-30px">
|
<div className={'ml-30px'}>
|
||||||
<div> </div>
|
<div> </div>
|
||||||
<Button type="primary" onClick={handleReportCreation}>
|
<Button type={'primary'} onClick={handleReportCreation}>
|
||||||
<span>Получить рапорт</span>
|
Получить рапорт ({pagesCount} стр.) {pagesCount > 100 && '!!!'}
|
||||||
<span className={"ml-5px"}>({pagesCount} стр.)</span>
|
|
||||||
<span
|
|
||||||
style={{ display: pagesCount > 100 ? "inline" : "none" }}
|
|
||||||
className={"ml-5px"}
|
|
||||||
>
|
|
||||||
!!!
|
|
||||||
</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import moment from 'moment'
|
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Modal, Input } from 'antd'
|
import { Modal, Input } from 'antd'
|
||||||
|
|
||||||
import { Table } from '../../components/Table'
|
import { Table } from '../../components/Table'
|
||||||
import { UserView } from '../../components/Views'
|
import { UserView } from '../../components/Views'
|
||||||
import { Grid, GridItem } from '../../components/Grid'
|
import { Grid, GridItem } from '../../components/Grid'
|
||||||
import { periodToString } from '../../utils/datetime'
|
import { formatDate, periodToString } from '../../utils'
|
||||||
|
|
||||||
export const setpointStatus = {
|
export const setpointStatus = {
|
||||||
0: 'Неизвестно',
|
0: 'Неизвестно',
|
||||||
@ -16,9 +16,9 @@ export const setpointStatus = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getSetpointStatus = (id) => {
|
export const getSetpointStatus = (id) => {
|
||||||
if (!id || Object.keys(setpointStatus).every((idx) => Number(idx) !== id))
|
if (id && Object.keys(setpointStatus).some((idx) => Number(idx) === id))
|
||||||
return setpointStatus[0]
|
|
||||||
return setpointStatus[id]
|
return setpointStatus[id]
|
||||||
|
return setpointStatus[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -27,14 +27,11 @@ const columns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const SetpointViewer = memo(({ setpoint, visible, onClose, setpointNames }) => {
|
export const SetpointViewer = memo(({ setpoint, visible, onClose, setpointNames }) => {
|
||||||
let date = moment(setpoint?.uploadDate).format('DD MMM YYYY, HH:mm:ss')
|
const date = formatDate(setpoint?.uploadDate)
|
||||||
let setpoints = []
|
const setpoints = Object.keys(setpoint?.setpoints ?? []).map((name) => ({
|
||||||
if (setpoint) {
|
|
||||||
setpoints = Object.keys(setpoint?.setpoints).map((name) => ({
|
|
||||||
name: setpointNames.find(spName => spName.value === name)?.label,
|
name: setpointNames.find(spName => spName.value === name)?.label,
|
||||||
value: setpoint.setpoints[name]
|
value: setpoint?.setpoints?.[name]
|
||||||
}))
|
}))
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -55,7 +52,7 @@ export const SetpointViewer = memo(({ setpoint, visible, onClose, setpointNames
|
|||||||
<GridItem row={3} col={2}>{getSetpointStatus(setpoint?.idState)}</GridItem>
|
<GridItem row={3} col={2}>{getSetpointStatus(setpoint?.idState)}</GridItem>
|
||||||
|
|
||||||
<GridItem row={4} col={1}>Период актуальности рекомендаций:</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={5} col={1}>Комментарий:</GridItem>
|
||||||
<GridItem row={6} col={1} colSpan={3}>
|
<GridItem row={6} col={1} colSpan={3}>
|
||||||
|
@ -122,13 +122,13 @@ export const WellCompositeSections = ({idWell, statsWells, selectedSections}) =>
|
|||||||
(text, item) => <Link to={`/well/${item?.id}`}>{text ?? '-'}</Link>
|
(text, item) => <Link to={`/well/${item?.id}`}>{text ?? '-'}</Link>
|
||||||
),
|
),
|
||||||
makeTextColumn('Секция', 'sectionType', filtersSectionsType, null, (text) => text ?? '-'),
|
makeTextColumn('Секция', 'sectionType', filtersSectionsType, null, (text) => text ?? '-'),
|
||||||
makeNumericColumnPlanFact('Глубина', 'sectionWellDepth', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('Глубина, м', 'sectionWellDepth', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('Продолжительность', 'sectionBuildDays', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('Продолжительность, ч', 'sectionBuildDays', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('МСП', 'sectionRateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('МСП, м/ч', 'sectionRateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('Рейсовая скорость', 'sectionRouteSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('Рейсовая скорость, м/ч', 'sectionRouteSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('Спуск КНБК', 'sectionBhaDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('Спуск КНБК, м/ч', 'sectionBhaDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('Подъем КНБК', 'sectionBhaUpSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('Подъем КНБК, м/ч', 'sectionBhaUpSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('Скорость спуска ОК', 'sectionCasingDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
makeNumericColumnPlanFact('Скорость спуска ОК, м/ч', 'sectionCasingDownSpeed', filtersMinMax, makeFilterMinMaxFunction),
|
||||||
makeNumericColumnPlanFact('НПВ, сут', 'nonProductiveTime', filtersMinMax, makeFilterMinMaxFunction, null, '70px'),
|
makeNumericColumnPlanFact('НПВ, сут', 'nonProductiveTime', filtersMinMax, makeFilterMinMaxFunction, null, '70px'),
|
||||||
{
|
{
|
||||||
title: 'TVD',
|
title: 'TVD',
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import {
|
import { DrillParamsService } from '../../services/api'
|
||||||
makeColumn,
|
|
||||||
makeNumericAvgRange
|
|
||||||
} from '../../components/Table'
|
|
||||||
import LoaderPortal from '../../components/LoaderPortal'
|
import LoaderPortal from '../../components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||||
import { EditableTable, SelectFromDictionary } from '../../components/Table'
|
import { EditableTable, SelectFromDictionary } from '../../components/Table'
|
||||||
import { DrillParamsService } from '../../services/api'
|
import { makeActionHandler, makeColumn, makeNumericAvgRange } from '../../components/Table'
|
||||||
|
|
||||||
import { dictionarySectionType, getByKeyOrReturnKey } from './dictionary'
|
import { dictionarySectionType, getByKeyOrReturnKey } from './dictionary'
|
||||||
|
|
||||||
export const columns = [
|
export const columns = [
|
||||||
@ -17,12 +15,11 @@ export const columns = [
|
|||||||
width: 160,
|
width: 160,
|
||||||
render: (_, record) => getByKeyOrReturnKey(dictionarySectionType, record.idWellSectionType)
|
render: (_, record) => getByKeyOrReturnKey(dictionarySectionType, record.idWellSectionType)
|
||||||
}),
|
}),
|
||||||
// makeNumericStartEnd('Глубина', 'depth'),
|
makeNumericAvgRange('Нагрузка, т', 'axialLoad', 1),
|
||||||
makeNumericAvgRange('Нагрузка', 'axialLoad', 1),
|
makeNumericAvgRange('Давление, атм', 'pressure', 1),
|
||||||
makeNumericAvgRange('Давление', 'pressure', 1),
|
makeNumericAvgRange('Момент на ВСП, кН·м', 'rotorTorque', 1),
|
||||||
makeNumericAvgRange('Момент на ВСП', 'rotorTorque', 1),
|
makeNumericAvgRange('Обороты на ВСП, об/мин', 'rotorSpeed', 1),
|
||||||
makeNumericAvgRange('Обороты на ВСП', 'rotorSpeed', 1),
|
makeNumericAvgRange('Расход, л/с', 'flow', 1)
|
||||||
makeNumericAvgRange('Расход', 'flow', 1)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const WellDrillParams = ({ idWell }) => {
|
export const WellDrillParams = ({ idWell }) => {
|
||||||
@ -40,24 +37,15 @@ export const WellDrillParams = ({idWell}) => {
|
|||||||
|
|
||||||
useEffect(updateParams, [idWell])
|
useEffect(updateParams, [idWell])
|
||||||
|
|
||||||
const onAdd = async (param) => {
|
const handlerProps = {
|
||||||
param.idWell = idWell
|
service: DrillParamsService,
|
||||||
await DrillParamsService.insert(idWell, param)
|
setLoader: setShowLoader,
|
||||||
updateParams()
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
|
onComplete: updateParams,
|
||||||
|
idWell
|
||||||
}
|
}
|
||||||
|
|
||||||
const onEdit = async (param) => {
|
const recordParser = (record) => ({ ...record, idWell })
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
@ -66,9 +54,9 @@ export const WellDrillParams = ({idWell}) => {
|
|||||||
bordered
|
bordered
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={params}
|
dataSource={params}
|
||||||
onRowAdd={onAdd}
|
onRowAdd={makeActionHandler('insert', handlerProps, recordParser)}
|
||||||
onRowEdit={onEdit}
|
onRowEdit={makeActionHandler('update', handlerProps, recordParser)}
|
||||||
onRowDelete={onDelete}
|
onRowDelete={makeActionHandler('delete', handlerProps, recordParser)}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
|
@ -1,37 +1,71 @@
|
|||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { Input } from 'antd'
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
DatePickerWrapper,
|
DatePickerWrapper,
|
||||||
SelectFromDictionary,
|
|
||||||
makeColumn,
|
makeColumn,
|
||||||
makeDateSorter,
|
makeDateSorter,
|
||||||
makeNumericColumnOptions} from "../../components/Table"
|
makeNumericColumnOptions,
|
||||||
|
makeSelectColumn,
|
||||||
|
makeActionHandler,
|
||||||
|
} from '../../components/Table'
|
||||||
import LoaderPortal from '../../components/LoaderPortal'
|
import LoaderPortal from '../../components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||||
import { WellOperationService} from '../../services/api'
|
import { WellOperationService} from '../../services/api'
|
||||||
import { dictionarySectionType, getByKeyOrReturnKey } from './dictionary'
|
import { formatDate } from '../../utils'
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input
|
||||||
|
|
||||||
const basePageSize = 160;
|
const basePageSize = 160
|
||||||
const format='YYYY.MM.DD HH:mm'
|
|
||||||
|
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}) => {
|
export const WellOperationsEditor = ({idWell, idType}) => {
|
||||||
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({current:1, pageSize:basePageSize})
|
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({current:1, pageSize:basePageSize})
|
||||||
const [paginationTotal, setPaginationTotal] = useState(0)
|
const [paginationTotal, setPaginationTotal] = useState(0)
|
||||||
const [dictionaryOperationCategory, setDictionaryOperationCategory] = useState(new Map())
|
|
||||||
const [operations, setOperations] = useState([])
|
const [operations, setOperations] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [columns, setColumns] = useState(defaultColumns)
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const categories = await WellOperationService.getCategories(idWell)
|
let categories = await WellOperationService.getCategories(idWell) ?? []
|
||||||
const dictCategories = new Map()
|
categories = categories.map((item) => ({ value: item.id, label: item.name }))
|
||||||
categories?.forEach((item) => dictCategories.set(item.id, item.name))
|
|
||||||
setDictionaryOperationCategory(dictCategories)
|
let sectionTypes = await WellOperationService.getSectionTypes(idWell) ?? []
|
||||||
}),[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(
|
const updateOperations = () => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -51,78 +85,39 @@ export const WellOperationsEditor = ({idWell, idType}) => {
|
|||||||
|
|
||||||
useEffect(updateOperations, [idWell, idType, pageNumAndPageSize])
|
useEffect(updateOperations, [idWell, idType, pageNumAndPageSize])
|
||||||
|
|
||||||
const columns = [
|
const handlerProps = {
|
||||||
makeColumn('Конструкция секции','idWellSectionType', {
|
service: WellOperationService,
|
||||||
editable:true,
|
setLoader: setShowLoader,
|
||||||
input:<SelectFromDictionary dictionary={dictionarySectionType}/>,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
width:160,
|
onComplete: updateOperations,
|
||||||
render:(_, record)=>getByKeyOrReturnKey(dictionarySectionType, record.idWellSectionType)
|
idWell
|
||||||
}),
|
|
||||||
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 onEdit= async (operation) => {
|
const recordParser = (record) => ({
|
||||||
if(!operation.id)
|
...record,
|
||||||
return
|
idType,
|
||||||
operation.idType = idType
|
wellDepth: +record.wellDepth,
|
||||||
operation.wellDepth = +operation.wellDepth
|
durationHours: +record.durationHours
|
||||||
operation.durationHours = +operation.durationHours
|
})
|
||||||
await WellOperationService.update(idWell, operation.id, operation)
|
|
||||||
updateOperations()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDelete= async (operation) => {
|
return (
|
||||||
if(!operation.id)
|
<LoaderPortal show={showLoader}>
|
||||||
return
|
|
||||||
await WellOperationService.delete(idWell, operation.id)
|
|
||||||
updateOperations()
|
|
||||||
}
|
|
||||||
|
|
||||||
return <LoaderPortal show={showLoader}>
|
|
||||||
<EditableTable
|
<EditableTable
|
||||||
size='small'
|
size={'small'}
|
||||||
bordered
|
bordered
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={operations}
|
dataSource={operations}
|
||||||
onRowAdd={onAdd}
|
onRowAdd={makeActionHandler('insertRange', handlerProps, recordParser)}
|
||||||
onRowEdit={onEdit}
|
onRowEdit={makeActionHandler('update', handlerProps, recordParser)}
|
||||||
onRowDelete={onDelete}
|
onRowDelete={makeActionHandler('delete', handlerProps)}
|
||||||
pagination={{
|
pagination={{
|
||||||
current: pageNumAndPageSize.current,
|
current: pageNumAndPageSize.current,
|
||||||
pageSize: pageNumAndPageSize.pageSize,
|
pageSize: pageNumAndPageSize.pageSize,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
total: paginationTotal,
|
total: paginationTotal,
|
||||||
onChange: (page, pageSize) => setPageNumAndPageSize({current: page, pageSize: pageSize})
|
onChange: (page, pageSize) => setPageNumAndPageSize({ current: page, pageSize })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
|
)
|
||||||
}
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import LoaderPortal from '../../components/LoaderPortal'
|
import LoaderPortal from '../../components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
import { invokeWebApiWrapperAsync } from '../../components/factory'
|
||||||
import { Table, makeColumn, makeColumnsPlanFact } from '../../components/Table'
|
import { Table, makeColumn, makeColumnsPlanFact } from '../../components/Table'
|
||||||
import { OperationStatService } from '../../services/api'
|
import { OperationStatService } from '../../services/api'
|
||||||
|
import { calcDuration } from '../../utils/datetime'
|
||||||
|
|
||||||
const makeNumberRender = value => {
|
const makeNumberRender = value => {
|
||||||
const v = +value
|
const v = +value
|
||||||
@ -13,23 +15,15 @@ const makeNumberRender = value => {
|
|||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
makeColumn('Тип секции', 'sectionType'),
|
makeColumn('Тип секции', 'sectionType'),
|
||||||
makeColumnsPlanFact('Глубина' ,'wellDepth', {render:makeNumberRender}),
|
makeColumnsPlanFact('Глубина, м' ,'wellDepth', { render: makeNumberRender }),
|
||||||
makeColumnsPlanFact('Часы' ,'duration', { render: makeNumberRender }),
|
makeColumnsPlanFact('Часы' ,'duration', { render: makeNumberRender }),
|
||||||
makeColumnsPlanFact('МСП' ,'rop', {render:makeNumberRender}),
|
makeColumnsPlanFact('МСП, м/ч' ,'rop', { render: makeNumberRender }),
|
||||||
makeColumnsPlanFact('Рейсовая скорость' ,'routeSpeed', {render:makeNumberRender}),
|
makeColumnsPlanFact('Рейсовая скорость, м/ч','routeSpeed', { render: makeNumberRender }),
|
||||||
makeColumnsPlanFact('Подъем КНБК' ,'bhaUpSpeed', {render:makeNumberRender}),
|
makeColumnsPlanFact('Подъем КНБК, м/ч' ,'bhaUpSpeed', { render: makeNumberRender }),
|
||||||
makeColumnsPlanFact('Спуск КНБК' ,'bhaDownSpeed', {render:makeNumberRender}),
|
makeColumnsPlanFact('Спуск КНБК, м/ч' ,'bhaDownSpeed', { render: makeNumberRender }),
|
||||||
makeColumnsPlanFact('Спуск ОК' ,'casingDownSpeed', {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}) => {
|
export const WellSectionsStat = ({idWell}) => {
|
||||||
const [sections, setSections] = useState([])
|
const [sections, setSections] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
@ -34,3 +34,34 @@ input.measure-input {
|
|||||||
.ml-10px {
|
.ml-10px {
|
||||||
margin-left: 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;
|
||||||
|
}
|
||||||
|
@ -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())
|
|
||||||
}
|
|
@ -1,10 +1,44 @@
|
|||||||
export const periodToString = (time?: number) => {
|
import moment from 'moment'
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export type { RawDate } from './DateTimeUtils'
|
export type { RawDate, timeInS } from './datetime'
|
||||||
export { isRawDate } from './DateTimeUtils'
|
export { isRawDate, formatDate, defaultFormat, periodToString } from './datetime'
|
||||||
|
|
||||||
export const headerHeight = 64
|
export const headerHeight = 64
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user