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

Мелкий рефракторинг
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 moment from 'moment'
import { formatDate } from './index'
import { defaultFormat } from '../../utils'
export type DatePickerWrapperProps = {
value: moment.Moment,
@ -13,7 +13,7 @@ export const DatePickerWrapper: React.FC<DatePickerWrapperProps> = ({value, onCh
allowClear={false}
defaultValue={moment()}
value={moment.utc(value).local()}
format={formatDate}
format={defaultFormat}
showTime
onChange={(date) => onChange(date)}
{...other}

View File

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

View File

@ -10,7 +10,6 @@ export { DatePickerWrapper } from './DatePickerWrapper'
export { SelectFromDictionary } from './SelectFromDictionary'
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
export const formatDate = 'YYYY.MM.DD HH:mm'
export const makeNumericRender = (fixed?: number) => (value: any, row: object): ReactNode => {
let val = '-'
@ -209,7 +208,7 @@ export const makeSelectColumn = <T extends unknown = string>(
other?: columnPropsOther
) => makeColumn(title, dataIndex, {
input: <Select options={options}/>,
render: (value) => options.find(option => option?.value === value)?.label ?? defaultValue ?? '--',
render: (value) => options?.find(option => option?.value === value)?.label ?? defaultValue ?? value ?? '--',
...other
})

View File

@ -9,7 +9,7 @@ import { WellIcon, WellIconState } from './icons/WellIcon'
import { ReactComponent as DepositIcon } from '../images/DepositIcon.svg'
import { ReactComponent as ClusterIcon } from '../images/ClusterIcon.svg'
import { DepositDto } from '../services/api'
import { RawDate } from '../utils/DateTimeUtils'
import { RawDate } from '../utils/'
export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown'
export const checkIsWellOnline = (lastTelemetryDate?: RawDate): boolean => {

View File

@ -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'
const notificationTypeDictionary = new Map([
@ -15,29 +15,28 @@ const notificationTypeDictionary = new Map([
* @param notifyType для параметра типа. Допустимые значение 'error', 'warning', 'info'
*/
export const notify = (body: string | any, notifyType: string = 'info', other?: any) => {
if(!body)
return
if (!body) return
const instance = notificationTypeDictionary.get(notifyType) ??
notificationTypeDictionary.get('open')
instance?.notifyInstance({
description: body,
message: instance.caption,
placement: "bottomRight",
placement: 'bottomRight',
duration: 10,
...other
})
}
type asyncFunction = (...args:any) => Promise<any|void>;
type asyncFunction = (...args: any) => Promise<any|void>
export const invokeWebApiWrapperAsync = async (
funcAsync: asyncFunction,
setShowLoader: Dispatch<SetStateAction<boolean>>,
errorNotifyText: (string | ((ex: unknown) => string))
setShowLoader?: Dispatch<SetStateAction<boolean>>,
errorNotifyText?: (string | ((ex: unknown) => string))
) => {
if(setShowLoader)
setShowLoader(true)
setShowLoader?.(true)
try{
await funcAsync()
} catch (ex) {
@ -49,48 +48,42 @@ export const invokeWebApiWrapperAsync = async (
else notify(errorNotifyText, 'error')
}
} finally {
if(setShowLoader)
setShowLoader(false)
setShowLoader?.(false)
}
}
export const download = async (url: string, fileName?: string) => {
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ' + localStorage['token']
Authorization: `Bearer ${localStorage['token']}`
},
method: 'Get'
})
if(response.status !== 200)
throw response
const requestFileName = decodeURI(fileName
??response.headers
.get('content-disposition')
?.split(';')
.splice(-1)[0]
.split("'")
.splice(-1)[0]
?? url.replace('\\','/')
.split('/')
.splice(-1)[0]
?? 'file')
const requestFileName = decodeURI(
fileName
?? response.headers.get('content-disposition')?.split(';').pop()?.split(`'`).pop()
?? url.replace('\\','/').split('/').pop()
?? 'file'
)
const blob = await response.blob()
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (e) {
let a = document.createElement('a');
a.href = (e.target?.result?.toString() ?? '');
a.download = requestFileName;
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
a.click();
a.remove();
};
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onload = (e) => {
const a = document.createElement('a')
a.href = (e.target?.result?.toString() ?? '')
a.download = requestFileName
document.body.appendChild(a) // we need to append the element to the dom -> otherwise it will not work in firefox
a.click()
a.remove()
}
}
export const upload = async (url: string, formData: FormData) => {
let response = await fetch(url, {
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ' + localStorage['token']
Authorization: `Bearer ${localStorage['token']}`
},
method: 'Post',
body: formData,
@ -115,20 +108,3 @@ export const formatBytes = (bytes:number) => {
else
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 ?? '-'),
makeGroupColumn('Фактические сроки', [
makeColumn('начало', 'factStart', { sorter: makeDateSorter('factStart'), render: getDate }),
makeColumn('окончание', 'factEnd', { sorter: makeDateSorter('factEnd'), render: getDate })
makeColumn('окончание', 'factEnd', { sorter: makeDateSorter('factEnd'), render: getDate }),
]),
makeNumericColumnPlanFact('Продолжительность', 'period', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('МСП', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Рейсовая скорость', 'routeSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Продолжительность, сут', 'period', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('МСП, м/ч', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('Рейсовая скорость, м/ч', 'routeSpeed', filtersMinMax, makeFilterMinMaxFunction),
makeNumericColumnPlanFact('НПВ, сут', 'notProductiveTime', filtersMinMax, makeFilterMinMaxFunction),
{
title: 'TVD',

View File

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

View File

@ -1,4 +1,3 @@
import moment from 'moment'
import { useState, useEffect } from 'react'
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
import {
@ -18,9 +17,7 @@ import { View } from './View'
import '../../styles/index.css'
import '../../styles/measure.css'
export const formatDate = (date) => date ?
moment.utc(date).local().format('YYYY.MM.DD HH:mm') : 'Нет данных'
import { formatDate } from '../../utils'
const createEditingColumns = (cols, renderDelegate) =>
cols.map(col => ({ render: renderDelegate, ...col }))
@ -145,8 +142,8 @@ export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButto
<CheckSquareOutlined className={'timeline-clock-icon'} />
}
>
<span className={item?.id === displayedValues?.id && 'selected-timeline'}>
{formatDate(item.timestamp)}
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : undefined}>
{formatDate(item.timestamp, true) ?? 'Нет данных'}
</span>
</Timeline.Item>
)}

View File

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

View File

@ -18,14 +18,12 @@ const defaultData = [
title: 'Замер бурового раствора',
columns: columnsDrillingFluid,
defaultValue: drillingFluidDefaultData,
},
{
}, {
idCategory: 2,
title: 'Шламограмма',
columns: columnsMudDiagram,
defaultValue: mudDiagramDefaultData,
},
{
}, {
idCategory: 3,
title: 'ННБ',
columns: columnsNnb,
@ -45,12 +43,13 @@ const defaultData = [
export const Measure = ({ idWell }) => {
const [showLoader, setShowLoader] = useState(false)
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(false)
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(true)
const [data, setData] = useState(defaultData)
const [tableIdx, setTableIdx] = useState(-1)
const updateCurrentValues = () => invokeWebApiWrapperAsync(
useEffect(() => invokeWebApiWrapperAsync(
async () => {
if (!isMeasuresUpdating) return
const measures = await MeasureService.getHisory(idWell)
setIsMeasuresUpdating(false)
@ -66,9 +65,7 @@ export const Measure = ({ idWell }) => {
},
setShowLoader,
`Не удалось загрузить последние данные по скважине ${idWell}`
)
useEffect(updateCurrentValues, [idWell, isMeasuresUpdating])
), [idWell, isMeasuresUpdating])
return (
<LoaderPortal show={showLoader}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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