На вкладку секции, режимы добавлены единицы измерения

Мелкий рефракторинг
This commit is contained in:
Александр Сироткин 2021-12-27 15:18:20 +05:00
parent 528b7e4f7b
commit c327240ea2
20 changed files with 413 additions and 467 deletions

View File

@ -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}

View File

@ -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))
} }
onComplete?.() onComplete?.()
}, },

View File

@ -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
}) })

View File

@ -9,7 +9,7 @@ import { WellIcon, WellIconState } from './icons/WellIcon'
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/'
export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown' export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown'
export const checkIsWellOnline = (lastTelemetryDate?: RawDate): boolean => { export const checkIsWellOnline = (lastTelemetryDate?: RawDate): boolean => {

View File

@ -1,12 +1,12 @@
import { Dispatch, SetStateAction } from "react" import { notification } from 'antd'
import { notification } from 'antd'; import { Dispatch, SetStateAction } from 'react'
import { FileInfoDto } from '../services/api' import { FileInfoDto } from '../services/api'
const notificationTypeDictionary = new Map([ const notificationTypeDictionary = new Map([
['error', {notifyInstance: notification.error, caption: 'Ошибка'}], ['error' , { notifyInstance: notification.error , caption: 'Ошибка' }],
['warning', {notifyInstance: notification.warning, caption: 'Предупреждение'}], ['warning', { notifyInstance: notification.warning, caption: 'Предупреждение' }],
['info', {notifyInstance: notification.info, caption: 'Инфо'}], ['info' , { notifyInstance: notification.info , caption: 'Инфо' }],
['open', {notifyInstance: notification.info, caption: ''}], ['open' , { notifyInstance: notification.info , caption: '' }],
]) ])
/** /**
@ -14,30 +14,29 @@ const notificationTypeDictionary = new Map([
* @param body string или ReactNode * @param body string или ReactNode
* @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) {
@ -48,49 +47,43 @@ export const invokeWebApiWrapperAsync = async (
notify(errorNotifyText(ex), 'error') notify(errorNotifyText(ex), 'error')
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,
@ -107,28 +100,11 @@ export const downloadFile = async (fileInfo: FileInfoDto) => {
} }
} }
export const formatBytes = (bytes:number) => { export const formatBytes = (bytes: number) => {
if(bytes < 1024) if(bytes < 1024)
return `${bytes.toFixed(0)}b` return `${bytes.toFixed(0)}b`
if(bytes < 1024*1024) if(bytes < 1024 * 1024)
return `${(bytes/1024).toFixed(2)}kb` return `${(bytes / 1024).toFixed(2)}kb`
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
} }

View File

@ -119,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',

View File

@ -2,8 +2,8 @@ import { Modal } from 'antd'
import { memo, useEffect, useState } from 'react' import { memo, useEffect, useState } from 'react'
import { Table } from '../../components/Table' import { Table } from '../../components/Table'
import { formatDate } from '../../utils'
import { formatDate } from './MeasureTable'
import { v } from './columnsCommon' import { v } from './columnsCommon'
const tableScroll = { y: 400, x: 1300 } const tableScroll = { y: 400, x: 1300 }
@ -12,7 +12,7 @@ const dateColumn = {
title: 'Дата', title: 'Дата',
key: 'date', key: 'date',
dataIndex: 'date', dataIndex: 'date',
render: formatDate, render: (date) => formatDate(date, true) ?? 'Нет данных',
fixed: 'left', fixed: 'left',
width: '8rem', width: '8rem',
} }

View File

@ -1,4 +1,3 @@
import moment from 'moment'
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 { import {
@ -18,9 +17,7 @@ import { View } from './View'
import '../../styles/index.css' import '../../styles/index.css'
import '../../styles/measure.css' import '../../styles/measure.css'
import { formatDate } from '../../utils'
export const formatDate = (date) => date ?
moment.utc(date).local().format('YYYY.MM.DD HH:mm') : 'Нет данных'
const createEditingColumns = (cols, renderDelegate) => const createEditingColumns = (cols, renderDelegate) =>
cols.map(col => ({ render: renderDelegate, ...col })) cols.map(col => ({ render: renderDelegate, ...col }))
@ -123,8 +120,8 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
<Button key={'edit'} className={'flex-1'} onClick={() => editTable('edit')} disabled={isDataDefault()}> <Button key={'edit'} className={'flex-1'} onClick={() => editTable('edit')} disabled={isDataDefault()}>
<EditOutlined /> <EditOutlined />
</Button> </Button>
<Popconfirm style={{flex: '1'}} title={'Удалить данные?'} onConfirm={markMeasuresAsDeleted} disabled={isDataDefault()}> <Popconfirm style={{ flex: '1' }} title={'Удалить данные?'} onConfirm={markMeasuresAsDeleted} disabled={isDataDefault()}>
<Button key={'delete'} onClick={() => setEditingActionName('delete')} disabled={isDataDefault()} > <Button key={'delete'} onClick={() => setEditingActionName('delete')} disabled={isDataDefault()}>
<DeleteOutlined style={{ margin:'auto 28px' }}/> <DeleteOutlined style={{ margin:'auto 28px' }}/>
</Button> </Button>
</Popconfirm> </Popconfirm>
@ -145,8 +142,8 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
<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>
)} )}

View File

@ -34,7 +34,7 @@ export const View = memo(({ columns, item }) => !item || !columns?.length ? (
<Form.Item <Form.Item
key={column.dataIndex} key={column.dataIndex}
name={column.dataIndex} name={column.dataIndex}
style={{ padding: 0 }} style={{ margin: 0 }}
> >
{column.render(item[column.dataIndex])} {column.render(item[column.dataIndex])}
</Form.Item> </Form.Item>

View File

@ -18,14 +18,12 @@ const defaultData = [
title: 'Замер бурового раствора', title: 'Замер бурового раствора',
columns: columnsDrillingFluid, columns: columnsDrillingFluid,
defaultValue: drillingFluidDefaultData, defaultValue: drillingFluidDefaultData,
}, }, {
{
idCategory: 2, idCategory: 2,
title: 'Шламограмма', title: 'Шламограмма',
columns: columnsMudDiagram, columns: columnsMudDiagram,
defaultValue: mudDiagramDefaultData, defaultValue: mudDiagramDefaultData,
}, }, {
{
idCategory: 3, idCategory: 3,
title: 'ННБ', title: 'ННБ',
columns: columnsNnb, columns: columnsNnb,
@ -45,12 +43,13 @@ const defaultData = [
export const 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)
@ -66,9 +65,7 @@ export const Measure = ({ idWell }) => {
}, },
setShowLoader, setShowLoader,
`Не удалось загрузить последние данные по скважине ${idWell}` `Не удалось загрузить последние данные по скважине ${idWell}`
) ), [idWell, isMeasuresUpdating])
useEffect(updateCurrentValues, [idWell, isMeasuresUpdating])
return ( return (
<LoaderPortal show={showLoader}> <LoaderPortal show={showLoader}>

View File

@ -1,81 +1,83 @@
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 { formatDate, periodToString } from '../../utils'
import { invokeWebApiWrapperAsync, downloadFile, formatTimespan} from "../../components/factory" import { ReportService } from '../../services/api'
import LoaderPortal from "../../components/LoaderPortal" import LoaderPortal from '../../components/LoaderPortal'
import moment from "moment" import { Table, makeDateSorter, makeNumericSorter } from '../../components/Table'
import { invokeWebApiWrapperAsync, downloadFile, formatTimespan } 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)
}, },
] ]
export const Reports = ({idWell}) => { 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(
const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell) async () => {
const reports = reportsResponse.map(r => ({...r, key: r.id?? r.name?? r.date})) const reportsResponse = await ReportService.getAllReportsNamesByWell(idWell)
setReports(reports) const reports = reportsResponse.map(r => ({ ...r, key: r.id ?? r.name ?? r.date }))
},setShowLoader), [idWell]) setReports(reports)
},
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>
)
} }

View File

@ -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,82 +69,68 @@ 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" )
const disabledDate = (current) => !current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
useEffect(() => invokeWebApiWrapperAsync(
async () => {
const datesRangeResponse = await ReportService.getReportsDateRange(idWell)
if (!datesRangeResponse?.from || !datesRangeResponse.to)
throw new Error('Формат ответа неверный!')
const datesRange = [
moment(datesRangeResponse.from),
moment(datesRangeResponse.to),
]
setAviableDateRange(datesRange)
const from = moment.max(moment(datesRange[1]).subtract(1, 'days'), datesRange[0])
setFilterDateRange([
from.startOf('day'),
moment(datesRange[1]).startOf('day'),
])
},
setShowLoader,
`Не удалось получить диапозон дат рапортов для скважины "${idWell}"`
), [idWell])
useEffect(() => invokeWebApiWrapperAsync(
async () => {
if (filterDateRange?.length !== 2) return
const pagesCount = await ReportService.getReportSize(
idWell,
step,
format,
filterDateRange[0].toISOString(),
filterDateRange[1].toISOString()
) )
console.log(error) setPagesCount(pagesCount)
} },
} setShowLoader,
`Не удалось получить предварительные параметры отчета c
const disabledDate = (current) => { ${filterDateRange[0].format(dateTimeFormat)} по
return current < aviableDateRange[0] || current > aviableDateRange[1] ${filterDateRange[1].format(dateTimeFormat)}`
} ), [filterDateRange, step, format, idWell])
useEffect(
() =>
invokeWebApiWrapperAsync(async () => {
const datesRangeResponse = await ReportService.getReportsDateRange(
idWell
)
const datesRange = [
moment(datesRangeResponse.from),
moment(datesRangeResponse.to),
]
setAviableDateRange(datesRange)
let from = moment(datesRangeResponse.to)
from = from.subtract(1, "days")
if (from < datesRange[0]) from = datesRange[0]
const filterDateDefaults = [
from.startOf("day"),
moment(datesRangeResponse.to).startOf("day"),
]
setFilterDateRange(filterDateDefaults)
}, setShowLoader),
[idWell]
)
useEffect(
() =>
invokeWebApiWrapperAsync(
async () => {
if (!filterDateRange || filterDateRange.length < 2) return
const begin = filterDateRange[0].toISOString()
const end = filterDateRange[1].toISOString()
const pagesCount = await ReportService.getReportSize(
idWell,
step,
format,
begin,
end
)
setPagesCount(pagesCount)
},
setShowLoader,
`Не удалось получить предварительные параметры отчета c
${filterDateRange[0].format(dateTimeFormat)} по
${filterDateRange[1].format(dateTimeFormat)}`
),
[filterDateRange, step, format, idWell]
)
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>&nbsp;</div> <div>&nbsp;</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>

View File

@ -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/UserView' import { UserView } from '../../components/UserView'
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) { name: setpointNames.find(spName => spName.value === name)?.label,
setpoints = Object.keys(setpoint?.setpoints).map((name) => ({ value: setpoint?.setpoints?.[name]
name: setpointNames.find(spName => spName.value === name)?.label, }))
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}>

View File

@ -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',

View File

@ -1,31 +1,28 @@
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 = [
makeColumn('Конструкция секции','idWellSectionType', { makeColumn('Конструкция секции','idWellSectionType', {
editable:true, editable: true,
input:<SelectFromDictionary dictionary={dictionarySectionType}/>, input: <SelectFromDictionary dictionary={dictionarySectionType}/>,
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 }) => {
const [params, setParams] = useState([]) const [params, setParams] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
@ -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>

View File

@ -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 <EditableTable
await WellOperationService.delete(idWell, operation.id) size={'small'}
updateOperations() bordered
} columns={columns}
dataSource={operations}
return <LoaderPortal show={showLoader}> onRowAdd={makeActionHandler('insertRange', handlerProps, recordParser)}
<EditableTable onRowEdit={makeActionHandler('update', handlerProps, recordParser)}
size='small' onRowDelete={makeActionHandler('delete', handlerProps)}
bordered pagination={{
columns={columns} current: pageNumAndPageSize.current,
dataSource={operations} pageSize: pageNumAndPageSize.pageSize,
onRowAdd={onAdd} showSizeChanger: false,
onRowEdit={onEdit} total: paginationTotal,
onRowDelete={onDelete} onChange: (page, pageSize) => setPageNumAndPageSize({ current: page, pageSize })
pagination={{ }}
current: pageNumAndPageSize.current,
pageSize: pageNumAndPageSize.pageSize,
showSizeChanger: false,
total: paginationTotal,
onChange: (page, pageSize) => setPageNumAndPageSize({current: page, pageSize: pageSize})
}}
/> />
</LoaderPortal> </LoaderPortal>
)
} }

View File

@ -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)

View File

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

View File

@ -1,10 +1,44 @@
export const periodToString = (time?: number) => { 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
} }

View File

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