forked from ddrilling/asb_cloud_front
Merge branch 'dev'
This commit is contained in:
commit
fd6c36b6d5
@ -2,7 +2,7 @@ import { memo, ReactNode } from 'react'
|
|||||||
import { Layout, LayoutProps } from 'antd'
|
import { Layout, LayoutProps } from 'antd'
|
||||||
|
|
||||||
import PageHeader from '@components/PageHeader'
|
import PageHeader from '@components/PageHeader'
|
||||||
import WellTreeSelector from '@components/WellTreeSelector'
|
import WellTreeSelector from '@components/selectors/WellTreeSelector'
|
||||||
|
|
||||||
export type LayoutPortalProps = LayoutProps & {
|
export type LayoutPortalProps = LayoutProps & {
|
||||||
title?: ReactNode
|
title?: ReactNode
|
||||||
|
25
src/components/Table/Columns/date.tsx
Normal file
25
src/components/Table/Columns/date.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { formatDate } from '@utils'
|
||||||
|
|
||||||
|
import makeColumn, { columnPropsOther } from '.'
|
||||||
|
import { DatePickerWrapper, makeDateSorter } from '..'
|
||||||
|
|
||||||
|
export const makeDateColumn = (
|
||||||
|
title: ReactNode,
|
||||||
|
key: string,
|
||||||
|
utc?: boolean,
|
||||||
|
format?: string,
|
||||||
|
other?: columnPropsOther
|
||||||
|
) => makeColumn(title, key, {
|
||||||
|
...other,
|
||||||
|
render: (date) => (
|
||||||
|
<div className={'text-align-r-container'}>
|
||||||
|
<span>{formatDate(date, utc, format) ?? '-'}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
sorter: makeDateSorter(key),
|
||||||
|
input: <DatePickerWrapper />,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default makeDateColumn
|
@ -2,6 +2,7 @@ import { ReactNode } from 'react'
|
|||||||
import { Rule } from 'antd/lib/form'
|
import { Rule } from 'antd/lib/form'
|
||||||
import { ColumnProps } from 'antd/lib/table'
|
import { ColumnProps } from 'antd/lib/table'
|
||||||
|
|
||||||
|
export { makeDateColumn } from './date'
|
||||||
export {
|
export {
|
||||||
RegExpIsFloat,
|
RegExpIsFloat,
|
||||||
makeNumericRender,
|
makeNumericRender,
|
||||||
|
@ -6,8 +6,8 @@ import moment, { Moment } from 'moment'
|
|||||||
import { defaultFormat } from '@utils'
|
import { defaultFormat } from '@utils'
|
||||||
|
|
||||||
export type DatePickerWrapperProps = PickerDateProps<Moment> & {
|
export type DatePickerWrapperProps = PickerDateProps<Moment> & {
|
||||||
value: Moment,
|
value?: Moment,
|
||||||
onChange: (date: Moment | null) => any
|
onChange?: (date: Moment | null) => any
|
||||||
isUTC?: boolean
|
isUTC?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ export const DatePickerWrapper = memo<DatePickerWrapperProps>(({ value, onChange
|
|||||||
allowClear={false}
|
allowClear={false}
|
||||||
format={defaultFormat}
|
format={defaultFormat}
|
||||||
defaultValue={moment()}
|
defaultValue={moment()}
|
||||||
onChange={(date) => onChange(date)}
|
onChange={(date) => onChange?.(date)}
|
||||||
value={isUTC ? moment.utc(value).local() : moment(value)}
|
value={isUTC ? moment.utc(value).local() : moment(value)}
|
||||||
{...other}
|
{...other}
|
||||||
/>
|
/>
|
||||||
|
@ -188,6 +188,7 @@ export const EditableTable = memo(({
|
|||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
onCell: (record) => ({
|
onCell: (record) => ({
|
||||||
|
...col.onCell?.(record),
|
||||||
editing: isEditing(record),
|
editing: isEditing(record),
|
||||||
record,
|
record,
|
||||||
dataIndex: col.dataIndex ?? col.key,
|
dataIndex: col.dataIndex ?? col.key,
|
||||||
|
@ -6,6 +6,7 @@ export {
|
|||||||
RegExpIsFloat,
|
RegExpIsFloat,
|
||||||
timezoneOptions,
|
timezoneOptions,
|
||||||
TimezoneSelect,
|
TimezoneSelect,
|
||||||
|
makeDateColumn,
|
||||||
makeGroupColumn,
|
makeGroupColumn,
|
||||||
makeColumn,
|
makeColumn,
|
||||||
makeColumnsPlanFact,
|
makeColumnsPlanFact,
|
||||||
|
34
src/components/selectors/TelemetrySelect.tsx
Normal file
34
src/components/selectors/TelemetrySelect.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Select, SelectProps } from 'antd'
|
||||||
|
import { memo } from 'react'
|
||||||
|
|
||||||
|
import { getTelemetryLabel } from '@components/views'
|
||||||
|
import { TelemetryDto } from '@api'
|
||||||
|
|
||||||
|
import '@styles/components/telemetry_select.less'
|
||||||
|
|
||||||
|
export type TelemetrySelectProps = SelectProps & {
|
||||||
|
telemetry?: TelemetryDto[],
|
||||||
|
value?: TelemetryDto ,
|
||||||
|
onChange?: (value?: TelemetryDto) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TelemetrySelect = memo<TelemetrySelectProps>(({ telemetry, value, onChange, ...other }) => (
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
value={value?.id}
|
||||||
|
onChange={(id) => onChange?.(telemetry?.find((row) => row.id === id))}
|
||||||
|
className={'telemetry_select'}
|
||||||
|
dropdownClassName={'telemetry_select'}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{telemetry?.map((row, i) => (
|
||||||
|
<Select.Option key={i} value={row.id}>
|
||||||
|
<span className={row?.info?.well ? 'telemetry_used' : 'telemetry_unused'}>
|
||||||
|
{getTelemetryLabel(row)}
|
||||||
|
</span>
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
))
|
||||||
|
|
||||||
|
export default TelemetrySelect
|
@ -7,9 +7,9 @@ import { RawValueType } from 'rc-tree-select/lib/TreeSelect'
|
|||||||
import { LabelInValueType } from 'rc-select/lib/Select'
|
import { LabelInValueType } from 'rc-select/lib/Select'
|
||||||
|
|
||||||
import { isRawDate } from '@utils'
|
import { isRawDate } from '@utils'
|
||||||
import LoaderPortal from './LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { WellIcon, WellIconState } from './icons'
|
import { WellIcon, WellIconState } from '@components/icons'
|
||||||
import { invokeWebApiWrapperAsync } from './factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DepositService, DepositDto } from '@api'
|
import { DepositService, DepositDto } from '@api'
|
||||||
|
|
||||||
import { ReactComponent as DepositIcon } from '@images/DepositIcon.svg'
|
import { ReactComponent as DepositIcon } from '@images/DepositIcon.svg'
|
@ -4,7 +4,7 @@ import { Tooltip } from 'antd'
|
|||||||
import { TelemetryDto, TelemetryInfoDto } from '@api'
|
import { TelemetryDto, TelemetryInfoDto } from '@api'
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
import { Grid, GridItem } from '@components/Grid'
|
||||||
|
|
||||||
const lables: Record<string, string> = {
|
export const lables: Record<string, string> = {
|
||||||
timeZoneId: 'Временная зона',
|
timeZoneId: 'Временная зона',
|
||||||
timeZoneOffsetTotalHours: 'Сдвиг временной зоны',
|
timeZoneOffsetTotalHours: 'Сдвиг временной зоны',
|
||||||
drillingStartDate: 'Начало бурения',
|
drillingStartDate: 'Начало бурения',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
@ -9,7 +10,6 @@ import {
|
|||||||
defaultPagination,
|
defaultPagination,
|
||||||
makeTimezoneColumn
|
makeTimezoneColumn
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminClusterService, AdminDepositService } from '@api'
|
import { AdminClusterService, AdminDepositService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
@ -22,8 +22,16 @@ export const ClusterController = memo(() => {
|
|||||||
const [deposits, setDeposits] = useState([])
|
const [deposits, setDeposits] = useState([])
|
||||||
const [clusters, setClusters] = useState([])
|
const [clusters, setClusters] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
const clusterColumns = [
|
const filteredClusters = useMemo(() => clusters.filter((cluster) => cluster && (!searchValue || [
|
||||||
|
cluster.caption ?? '',
|
||||||
|
cluster.latitude?.toString ?? '',
|
||||||
|
cluster.longitude?.toString() ?? '',
|
||||||
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||||
|
), [clusters, searchValue])
|
||||||
|
|
||||||
|
const clusterColumns = useMemo(() => [
|
||||||
makeSelectColumn('Месторождение', 'idDeposit', deposits, '--', {
|
makeSelectColumn('Месторождение', 'idDeposit', deposits, '--', {
|
||||||
width: 200,
|
width: 200,
|
||||||
editable: true,
|
editable: true,
|
||||||
@ -38,7 +46,7 @@ export const ClusterController = memo(() => {
|
|||||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
||||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
||||||
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
||||||
]
|
], [deposits])
|
||||||
|
|
||||||
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -63,27 +71,35 @@ export const ClusterController = memo(() => {
|
|||||||
|
|
||||||
useEffect(updateTable, [updateTable])
|
useEffect(updateTable, [updateTable])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminClusterService,
|
service: AdminClusterService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateTable,
|
onComplete: updateTable,
|
||||||
}
|
}), [updateTable])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по полям: Название, Долгота, Широта)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
size={'small'}
|
|
||||||
bordered
|
bordered
|
||||||
dataSource={clusters}
|
size={'small'}
|
||||||
|
loading={showLoader}
|
||||||
columns={clusterColumns}
|
columns={clusterColumns}
|
||||||
|
dataSource={filteredClusters}
|
||||||
pagination={defaultPagination}
|
pagination={defaultPagination}
|
||||||
onRowAdd={hasPermission('AdminCluster.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление куста')}
|
onRowAdd={hasPermission('AdminCluster.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление куста')}
|
||||||
onRowEdit={hasPermission('AdminCluster.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование куста')}
|
onRowEdit={hasPermission('AdminCluster.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование куста')}
|
||||||
onRowDelete={hasPermission('AdminCluster.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление куста')}
|
onRowDelete={hasPermission('AdminCluster.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление куста')}
|
||||||
tableName={'admin_cluster_controller'}
|
tableName={'admin_cluster_controller'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
@ -8,7 +9,6 @@ import {
|
|||||||
makeSelectColumn,
|
makeSelectColumn,
|
||||||
defaultPagination
|
defaultPagination
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminCompanyService, AdminCompanyTypeService } from '@api'
|
import { AdminCompanyService, AdminCompanyTypeService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
@ -20,6 +20,11 @@ export const CompanyController = memo(() => {
|
|||||||
const [columns, setColumns] = useState([])
|
const [columns, setColumns] = useState([])
|
||||||
const [companies, setCompanies] = useState([])
|
const [companies, setCompanies] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
|
const filteredCompanies = useMemo(() => companies.filter((company) => company && (!searchValue ||
|
||||||
|
company.caption?.toLowerCase()?.includes(searchValue.toLowerCase())
|
||||||
|
)), [companies, searchValue])
|
||||||
|
|
||||||
const updateTable = useCallback(async () => {
|
const updateTable = useCallback(async () => {
|
||||||
const companies = await AdminCompanyService.getAll()
|
const companies = await AdminCompanyService.getAll()
|
||||||
@ -53,7 +58,7 @@ export const CompanyController = memo(() => {
|
|||||||
'Получение списка типов команд'
|
'Получение списка типов команд'
|
||||||
), [updateTable])
|
), [updateTable])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminCompanyService,
|
service: AdminCompanyService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
@ -63,22 +68,30 @@ export const CompanyController = memo(() => {
|
|||||||
`Не удалось обновить список компаний`,
|
`Не удалось обновить список компаний`,
|
||||||
'Получение списка компаний'
|
'Получение списка компаний'
|
||||||
),
|
),
|
||||||
}
|
}), [updateTable])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по полю Название)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
size={'small'}
|
|
||||||
bordered
|
bordered
|
||||||
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={companies}
|
loading={showLoader}
|
||||||
|
dataSource={filteredCompanies}
|
||||||
pagination={defaultPagination}
|
pagination={defaultPagination}
|
||||||
onRowAdd={hasPermission('AdminCompany.edit') && makeActionHandler('insert', handlerProps, null, 'Добавлениее компаний')}
|
onRowAdd={hasPermission('AdminCompany.edit') && makeActionHandler('insert', handlerProps, null, 'Добавлениее компаний')}
|
||||||
onRowEdit={hasPermission('AdminCompany.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование команий')}
|
onRowEdit={hasPermission('AdminCompany.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование команий')}
|
||||||
onRowDelete={hasPermission('AdminCompany.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление компаний')}
|
onRowDelete={hasPermission('AdminCompany.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление компаний')}
|
||||||
tableName={'admin_company_controller'}
|
tableName={'admin_company_controller'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
@ -7,7 +8,6 @@ import {
|
|||||||
makeStringSorter,
|
makeStringSorter,
|
||||||
defaultPagination
|
defaultPagination
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminCompanyTypeService } from '@api'
|
import { AdminCompanyTypeService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
@ -26,6 +26,11 @@ const columns = [
|
|||||||
export const CompanyTypeController = memo(() => {
|
export const CompanyTypeController = memo(() => {
|
||||||
const [companyTypes, setCompanyTypes] = useState([])
|
const [companyTypes, setCompanyTypes] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
|
const filteredCompanyTypes = useMemo(() => companyTypes.filter((companyType) => companyType && (!searchValue ||
|
||||||
|
companyType.caption?.toLowerCase()?.includes(searchValue.toLowerCase())
|
||||||
|
)), [companyTypes, searchValue])
|
||||||
|
|
||||||
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async() => {
|
async() => {
|
||||||
@ -39,27 +44,35 @@ export const CompanyTypeController = memo(() => {
|
|||||||
|
|
||||||
useEffect(updateTable, [updateTable])
|
useEffect(updateTable, [updateTable])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminCompanyTypeService,
|
service: AdminCompanyTypeService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateTable,
|
onComplete: updateTable,
|
||||||
}
|
}), [updateTable])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по полю Название)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
bordered
|
bordered
|
||||||
size={'small'}
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={companyTypes}
|
loading={showLoader}
|
||||||
pagination={defaultPagination}
|
pagination={defaultPagination}
|
||||||
|
dataSource={filteredCompanyTypes}
|
||||||
onRowAdd={hasPermission('AdminCompanyType.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление типа компаний')}
|
onRowAdd={hasPermission('AdminCompanyType.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление типа компаний')}
|
||||||
onRowEdit={hasPermission('AdminCompanyType.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование типа компаний')}
|
onRowEdit={hasPermission('AdminCompanyType.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование типа компаний')}
|
||||||
onRowDelete={hasPermission('AdminCompanyType.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление типа компаний')}
|
onRowDelete={hasPermission('AdminCompanyType.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление типа компаний')}
|
||||||
tableName={'admin_company_type_controller'}
|
tableName={'admin_company_type_controller'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { EditableTable, makeColumn, makeActionHandler, defaultPagination, makeTimezoneColumn } from '@components/Table'
|
import { EditableTable, makeColumn, makeActionHandler, defaultPagination, makeTimezoneColumn } from '@components/Table'
|
||||||
import { AdminDepositService } from '@api'
|
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
import { min1 } from '@utils/validationRules'
|
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission } from '@utils/permissions'
|
||||||
|
import { min1 } from '@utils/validationRules'
|
||||||
|
import { arrayOrDefault } from '@utils'
|
||||||
|
import { AdminDepositService } from '@api'
|
||||||
|
|
||||||
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
|
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
|
||||||
|
|
||||||
@ -20,6 +20,14 @@ const depositColumns = [
|
|||||||
export const DepositController = memo(() => {
|
export const DepositController = memo(() => {
|
||||||
const [deposits, setDeposits] = useState([])
|
const [deposits, setDeposits] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
|
const filteredDeposits = useMemo(() => deposits.filter((deposit) => deposit && (!searchValue || [
|
||||||
|
deposit.caption ?? '',
|
||||||
|
deposit.latitude?.toString() ?? '',
|
||||||
|
deposit.longitude?.toString() ?? '',
|
||||||
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||||
|
), [deposits, searchValue])
|
||||||
|
|
||||||
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async() => {
|
async() => {
|
||||||
@ -33,27 +41,35 @@ export const DepositController = memo(() => {
|
|||||||
|
|
||||||
useEffect(updateTable, [updateTable])
|
useEffect(updateTable, [updateTable])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminDepositService,
|
service: AdminDepositService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateTable,
|
onComplete: updateTable,
|
||||||
}
|
}), [updateTable])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по полям: Название, Долгота, Широта)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
size={'small'}
|
|
||||||
bordered
|
bordered
|
||||||
dataSource={deposits}
|
size={'small'}
|
||||||
|
loading={showLoader}
|
||||||
columns={depositColumns}
|
columns={depositColumns}
|
||||||
|
dataSource={filteredDeposits}
|
||||||
pagination={defaultPagination}
|
pagination={defaultPagination}
|
||||||
onRowAdd={hasPermission('AdminDeposit.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление месторождения')}
|
onRowAdd={hasPermission('AdminDeposit.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление месторождения')}
|
||||||
onRowEdit={hasPermission('AdminDeposit.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование месторождения')}
|
onRowEdit={hasPermission('AdminDeposit.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование месторождения')}
|
||||||
onRowDelete={hasPermission('AdminDeposit.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление месторождения')}
|
onRowDelete={hasPermission('AdminDeposit.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление месторождения')}
|
||||||
tableName={'admin_deposit_controller'}
|
tableName={'admin_deposit_controller'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
@ -6,7 +7,6 @@ import {
|
|||||||
makeColumn,
|
makeColumn,
|
||||||
makeStringSorter
|
makeStringSorter
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { AdminPermissionService } from '@api'
|
import { AdminPermissionService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
@ -27,8 +27,15 @@ const columns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const PermissionController = memo(() => {
|
export const PermissionController = memo(() => {
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
|
||||||
const [permissions, setPermissions] = useState([])
|
const [permissions, setPermissions] = useState([])
|
||||||
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
|
const filteredPermissions = useMemo(() => permissions.filter((permission) => permission && (!searchValue || [
|
||||||
|
permission.name ?? '',
|
||||||
|
permission.description ?? '',
|
||||||
|
].join(' ').includes(searchValue.toLowerCase()))
|
||||||
|
), [permissions, searchValue])
|
||||||
|
|
||||||
const updateTable = useCallback(async () => invokeWebApiWrapperAsync(
|
const updateTable = useCallback(async () => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -42,27 +49,35 @@ export const PermissionController = memo(() => {
|
|||||||
|
|
||||||
useEffect(() => updateTable(), [updateTable])
|
useEffect(() => updateTable(), [updateTable])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminPermissionService,
|
service: AdminPermissionService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateTable
|
onComplete: updateTable
|
||||||
}
|
}), [updateTable])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по полям: Название, Описание)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
bordered
|
bordered
|
||||||
size={'small'}
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={permissions}
|
loading={showLoader}
|
||||||
|
dataSource={filteredPermissions}
|
||||||
pagination={{ showSizeChanger: true }}
|
pagination={{ showSizeChanger: true }}
|
||||||
onRowAdd={hasPermission('AdminPermission.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление права')}
|
onRowAdd={hasPermission('AdminPermission.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление права')}
|
||||||
onRowEdit={hasPermission('AdminPermission.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование права')}
|
onRowEdit={hasPermission('AdminPermission.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование права')}
|
||||||
onRowDelete={hasPermission('AdminPermission.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление права')}
|
onRowDelete={hasPermission('AdminPermission.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление права')}
|
||||||
tableName={'admin_permission_controller'}
|
tableName={'admin_permission_controller'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { PermissionView, RoleView } from '@components/views'
|
import { PermissionView, RoleView } from '@components/views'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { EditableTable, makeActionHandler, makeColumn, makeTagColumn } from '@components/Table'
|
import { EditableTable, makeActionHandler, makeTagColumn, makeTextColumn } from '@components/Table'
|
||||||
import { AdminPermissionService, AdminUserRoleService } from '@api'
|
import { AdminPermissionService, AdminUserRoleService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
import { min1 } from '@utils/validationRules'
|
import { min1 } from '@utils/validationRules'
|
||||||
@ -13,16 +13,14 @@ export const RoleController = memo(() => {
|
|||||||
const [permissions, setPermissions] = useState([])
|
const [permissions, setPermissions] = useState([])
|
||||||
const [roles, setRoles] = useState([])
|
const [roles, setRoles] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [columns, setColumns] = useState([])
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
const loadRoles = useCallback(async () => {
|
const filteredRoles = useMemo(() => roles.filter((role) => role && (!searchValue ||
|
||||||
const roles = await AdminUserRoleService.getAll()
|
role.caption?.toLowerCase()?.includes(searchValue.toLowerCase())
|
||||||
setRoles(arrayOrDefault(roles))
|
)), [roles, searchValue])
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const columns = useMemo(() => [
|
||||||
setColumns([
|
makeTextColumn('Название', 'caption', null, null, null, { width: 100, editable: true, formItemRules: min1 }),
|
||||||
makeColumn('Название', 'caption', { width: 100, editable: true, formItemRules: min1 }),
|
|
||||||
makeTagColumn('Включённые роли', 'roles', roles, 'id', 'caption', {
|
makeTagColumn('Включённые роли', 'roles', roles, 'id', 'caption', {
|
||||||
width: 400,
|
width: 400,
|
||||||
editable: true,
|
editable: true,
|
||||||
@ -33,8 +31,12 @@ export const RoleController = memo(() => {
|
|||||||
editable: true,
|
editable: true,
|
||||||
render: (permission) => <PermissionView info={permission} />,
|
render: (permission) => <PermissionView info={permission} />,
|
||||||
}),
|
}),
|
||||||
])
|
], [roles, permissions])
|
||||||
}, [roles, permissions])
|
|
||||||
|
const loadRoles = useCallback(async () => {
|
||||||
|
const roles = await AdminUserRoleService.getAll()
|
||||||
|
setRoles(arrayOrDefault(roles))
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -47,7 +49,7 @@ export const RoleController = memo(() => {
|
|||||||
'Получение списка ролей'
|
'Получение списка ролей'
|
||||||
), [loadRoles])
|
), [loadRoles])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminUserRoleService,
|
service: AdminUserRoleService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
@ -55,23 +57,31 @@ export const RoleController = memo(() => {
|
|||||||
loadRoles,
|
loadRoles,
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить список ролей`,
|
`Не удалось загрузить список ролей`,
|
||||||
'Получение списка ролей'
|
'Получение списка ролей',
|
||||||
)
|
)
|
||||||
}
|
}), [loadRoles])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по полю Название)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
bordered
|
bordered
|
||||||
size={'small'}
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={roles}
|
loading={showLoader}
|
||||||
|
dataSource={filteredRoles}
|
||||||
onRowAdd={hasPermission('AdminUserRole.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление роли')}
|
onRowAdd={hasPermission('AdminUserRole.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление роли')}
|
||||||
onRowEdit={hasPermission('AdminUserRole.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование роли')}
|
onRowEdit={hasPermission('AdminUserRole.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование роли')}
|
||||||
onRowDelete={hasPermission('AdminUserRole.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление роли')}
|
onRowDelete={hasPermission('AdminUserRole.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление роли')}
|
||||||
tableName={'admin_role_controller'}
|
tableName={'admin_role_controller'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
136
src/pages/AdminPanel/Telemetry/TelemetryMerger.jsx
Normal file
136
src/pages/AdminPanel/Telemetry/TelemetryMerger.jsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { PullRequestOutlined } from '@ant-design/icons'
|
||||||
|
import { Button, Descriptions, Popconfirm } from 'antd'
|
||||||
|
|
||||||
|
import { Grid, GridItem } from '@components/Grid'
|
||||||
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { lables } from '@components/views/TelemetryView'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||||
|
import { AdminTelemetryService } from '@api'
|
||||||
|
import { arrayOrDefault } from '@utils'
|
||||||
|
|
||||||
|
const { Item } = Descriptions
|
||||||
|
|
||||||
|
export const TelemetryInfo = memo(({ info, danger, ...other }) => (
|
||||||
|
<Descriptions
|
||||||
|
bordered
|
||||||
|
column={1}
|
||||||
|
size={'small'}
|
||||||
|
style={{ background: 'white' }}
|
||||||
|
className={'telemetry_merger_info'}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{Object.keys({ ...lables, ...info }).map(key => (
|
||||||
|
<Item
|
||||||
|
key={key}
|
||||||
|
label={lables[key] ?? key}
|
||||||
|
style={{ color: danger === true || danger?.includes(key) ? 'red' : 'black' }}
|
||||||
|
>{info?.[key] ?? '-'}</Item>
|
||||||
|
))}
|
||||||
|
</Descriptions>
|
||||||
|
))
|
||||||
|
|
||||||
|
export const TelemetryMerger = memo(() => {
|
||||||
|
const [primary, setPrimary] = useState(null)
|
||||||
|
const [secondary, setSecondary] = useState(null)
|
||||||
|
const [telemetry, setTelemetry] = useState([])
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isMerging, setIsMerging] = useState(false)
|
||||||
|
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
const danger = useMemo(() => [
|
||||||
|
primary?.info?.well !== secondary?.info?.well && 'well',
|
||||||
|
primary?.info?.cluster !== secondary?.info?.cluster && 'cluster',
|
||||||
|
primary?.info?.deposit !== secondary?.info?.deposit && 'deposit',
|
||||||
|
], [primary, secondary])
|
||||||
|
|
||||||
|
const updateTelemetry = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const telemetry = arrayOrDefault(await AdminTelemetryService.getAll())
|
||||||
|
setTelemetry(telemetry)
|
||||||
|
},
|
||||||
|
setIsLoading,
|
||||||
|
'Не удалось загрузить список телеметрий',
|
||||||
|
'Получение списка телеметрий',
|
||||||
|
), [])
|
||||||
|
|
||||||
|
const mergeTelemetry = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
await new Promise(res => setTimeout(res, 1000))
|
||||||
|
/// await AdminTelemetryService.mergeTelemetries(secondary.id, primary.id)
|
||||||
|
await updateTelemetry()
|
||||||
|
},
|
||||||
|
setIsMerging,
|
||||||
|
'Не удалось объединить телеметрии',
|
||||||
|
'Объединение телеметрий',
|
||||||
|
), [updateTelemetry])
|
||||||
|
|
||||||
|
useEffect(() => updateTelemetry(), [updateTelemetry])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const query = new URLSearchParams(location.search)
|
||||||
|
const primaryId = parseInt(query.get('primary') ?? null)
|
||||||
|
const secondaryId = parseInt(query.get('secondary') ?? null)
|
||||||
|
const primary = isNaN(primaryId) ? null : telemetry.find((t) => t.id === primaryId)
|
||||||
|
const secondary = isNaN(secondaryId) ? null : telemetry.find((t) => t.id === secondaryId)
|
||||||
|
console.log([primary, secondary])
|
||||||
|
setPrimary(primary)
|
||||||
|
setSecondary(secondary)
|
||||||
|
}, [location, telemetry])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoaderPortal show={isLoading}>
|
||||||
|
<div className={'description'} style={{ marginTop: '15px' }}>{
|
||||||
|
///TODO: Добавить описание
|
||||||
|
}</div>
|
||||||
|
<Grid>
|
||||||
|
<GridItem col={1} row={1}>Результирующая телеметрия</GridItem>
|
||||||
|
<GridItem col={2} row={1}>Исходная телеметрия</GridItem>
|
||||||
|
<GridItem col={1} row={2}>
|
||||||
|
<TelemetrySelect
|
||||||
|
value={primary}
|
||||||
|
disabled={isMerging}
|
||||||
|
telemetry={telemetry}
|
||||||
|
onChange={setPrimary}
|
||||||
|
style={{ width: '100%', marginRight: '15px' }}
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem col={2} row={2}>
|
||||||
|
<TelemetrySelect
|
||||||
|
value={secondary}
|
||||||
|
disabled={isMerging}
|
||||||
|
telemetry={telemetry}
|
||||||
|
onChange={setSecondary}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem col={3} row={2}>
|
||||||
|
<Popconfirm
|
||||||
|
disabled={isMerging || !primary || !secondary}
|
||||||
|
title={'Исходная телеметрия будет удалена после объединения. Вы уверены?'}
|
||||||
|
okText={'Объединить'}
|
||||||
|
onConfirm={mergeTelemetry}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type={'primary'}
|
||||||
|
icon={<PullRequestOutlined />}
|
||||||
|
disabled={!primary || !secondary}
|
||||||
|
loading={isMerging}
|
||||||
|
>Объединить</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem col={1} row={3}>
|
||||||
|
<TelemetryInfo info={primary?.info} danger={danger} />
|
||||||
|
</GridItem>
|
||||||
|
<GridItem col={2} row={3}>
|
||||||
|
<TelemetryInfo info={secondary?.info} danger={danger} />
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
</LoaderPortal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TelemetryMerger
|
121
src/pages/AdminPanel/Telemetry/TelemetryViewer.jsx
Normal file
121
src/pages/AdminPanel/Telemetry/TelemetryViewer.jsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { PullRequestOutlined } from '@ant-design/icons'
|
||||||
|
import { Button, Input } from 'antd'
|
||||||
|
|
||||||
|
import {
|
||||||
|
defaultPagination,
|
||||||
|
makeColumn,
|
||||||
|
makeDateSorter,
|
||||||
|
makeNumericColumn,
|
||||||
|
makeNumericRender,
|
||||||
|
makeTextColumn,
|
||||||
|
Table
|
||||||
|
} from '@components/Table'
|
||||||
|
import Poprompt from '@components/selectors/Poprompt'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { AdminTelemetryService } from '@api'
|
||||||
|
import { arrayOrDefault } from '@utils'
|
||||||
|
import { useHistory } from 'react-router-dom'
|
||||||
|
|
||||||
|
export const TelemetryController = memo(() => {
|
||||||
|
const [telemetryData, setTelemetryData] = useState([])
|
||||||
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
|
const history = useHistory()
|
||||||
|
|
||||||
|
const toMerger = useCallback((type, id) => () => history.push(`/admin/telemetry/merger/?${type}=${id}`), [history])
|
||||||
|
|
||||||
|
const mergeRender = useCallback((value, record) => (
|
||||||
|
<Poprompt
|
||||||
|
placement={'topLeft'}
|
||||||
|
buttonProps={{
|
||||||
|
icon: <PullRequestOutlined />,
|
||||||
|
size: 'small',
|
||||||
|
danger: !!value,
|
||||||
|
}}
|
||||||
|
footer={(
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<Button onClick={toMerger('primary', record.id)}>Основная</Button>
|
||||||
|
<Button onClick={toMerger('secondary', record.id)} style={{ marginLeft: '8px' }}>Сливаемая</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>Вы собираетесь использовать данную телеметрию для слияния</p>
|
||||||
|
{record.realWell && (
|
||||||
|
<p style={{ color: 'red' }}>Внимание! Телеметрии назначена скважина!</p>
|
||||||
|
)}
|
||||||
|
</Poprompt>
|
||||||
|
), [toMerger])
|
||||||
|
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
makeColumn('', 'hasParent', { render: mergeRender }),
|
||||||
|
makeNumericColumn('ID', 'id', null, null, makeNumericRender(0)),
|
||||||
|
makeTextColumn('UID', 'remoteUid'),
|
||||||
|
makeTextColumn('Назначена на скважину', 'realWell'),
|
||||||
|
makeTextColumn('Дата начала бурения', 'drillingStartDate', null, makeDateSorter('drillingStartDate')),
|
||||||
|
makeTextColumn('Часовой пояс', 'timeZoneId'),
|
||||||
|
makeTextColumn('Скважина', 'well'),
|
||||||
|
makeTextColumn('Куст', 'cluster'),
|
||||||
|
makeTextColumn('Месторождение', 'deposit'),
|
||||||
|
makeTextColumn('Заказчик', 'customer'),
|
||||||
|
makeTextColumn('Комментарий', 'comment'),
|
||||||
|
makeTextColumn('Версия HMI', 'hmiVersion'),
|
||||||
|
makeTextColumn('Версия САУБ', 'saubPlcVersion'),
|
||||||
|
makeTextColumn('Версия Спин Мастер', 'spinPlcVersion'),
|
||||||
|
], [mergeRender])
|
||||||
|
|
||||||
|
const filteredTelemetryData = useMemo(() => telemetryData.filter((telemetry) => telemetry && (!searchValue || [
|
||||||
|
telemetry.id?.toString() ?? '',
|
||||||
|
telemetry.remoteUid ?? '',
|
||||||
|
telemetry.realWell ?? '',
|
||||||
|
telemetry.drillingStartDate ?? '',
|
||||||
|
telemetry.well ?? '',
|
||||||
|
telemetry.cluster ?? '',
|
||||||
|
telemetry.deposit ?? '',
|
||||||
|
telemetry.customer ?? '',
|
||||||
|
telemetry.comment ?? '',
|
||||||
|
telemetry.hmiVersion ?? '',
|
||||||
|
telemetry.saubPlcVersion ?? '',
|
||||||
|
telemetry.spinPlcVersion ?? '',
|
||||||
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||||
|
), [telemetryData, searchValue])
|
||||||
|
|
||||||
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const telemetryData = arrayOrDefault(await AdminTelemetryService.getAll())
|
||||||
|
setTelemetryData(telemetryData.map((telemetry) => ({
|
||||||
|
...(telemetry?.info ?? []),
|
||||||
|
id: telemetry?.id,
|
||||||
|
remoteUid: telemetry?.remoteUid,
|
||||||
|
realWell: telemetry?.well?.caption,
|
||||||
|
})))
|
||||||
|
},
|
||||||
|
setShowLoader,
|
||||||
|
`Не удалось загрузить список телеметрии скважин`,
|
||||||
|
'Полученик списка телеметрии скважин'
|
||||||
|
), [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по всем полям)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
|
<Table
|
||||||
|
bordered
|
||||||
|
size={'small'}
|
||||||
|
columns={columns}
|
||||||
|
loading={showLoader}
|
||||||
|
pagination={defaultPagination}
|
||||||
|
dataSource={filteredTelemetryData}
|
||||||
|
tableName={'admin_telemetry_controller'}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TelemetryController
|
42
src/pages/AdminPanel/Telemetry/index.jsx
Normal file
42
src/pages/AdminPanel/Telemetry/index.jsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Layout, Menu } from 'antd'
|
||||||
|
import { lazy, memo, Suspense } from 'react'
|
||||||
|
import { Switch, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private'
|
||||||
|
|
||||||
|
import { SuspenseFallback } from '@pages/SuspenseFallback'
|
||||||
|
|
||||||
|
const TelemetryViewer = lazy(() => import('./TelemetryViewer'))
|
||||||
|
const TelemetryMerger = lazy(() => import('./TelemetryMerger'))
|
||||||
|
|
||||||
|
const rootPath = '/admin/telemetry'
|
||||||
|
|
||||||
|
export const Telemetry = memo(() => {
|
||||||
|
const { tab } = useParams()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Menu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
|
||||||
|
<PrivateMenuItem.Link root={rootPath} key={'viewer'} path={'viewer'} title={'Просмотр'} />
|
||||||
|
<PrivateMenuItem.Link root={rootPath} key={'merger'} path={'merger'} title={'Объединение'} />
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<Layout.Content className={'site-layout-background'}>
|
||||||
|
<Suspense fallback={<SuspenseFallback />}>
|
||||||
|
<Switch>
|
||||||
|
<PrivateRoute path={`${rootPath}/viewer`} component={TelemetryViewer} />
|
||||||
|
<PrivateRoute path={`${rootPath}/merger`} component={TelemetryMerger} />
|
||||||
|
<PrivateDefaultRoute urls={[
|
||||||
|
`${rootPath}/viewer`,
|
||||||
|
`${rootPath}/merger`,
|
||||||
|
]}/>
|
||||||
|
</Switch>
|
||||||
|
</Suspense>
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Telemetry
|
@ -1,65 +0,0 @@
|
|||||||
import { memo, useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import {
|
|
||||||
defaultPagination,
|
|
||||||
makeDateSorter,
|
|
||||||
makeNumericColumn,
|
|
||||||
makeNumericRender,
|
|
||||||
makeTextColumn,
|
|
||||||
Table
|
|
||||||
} from '@components/Table'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { AdminTelemetryService } from '@api'
|
|
||||||
import { arrayOrDefault } from '@utils'
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
makeNumericColumn('ID', 'id', null, null, makeNumericRender(0)),
|
|
||||||
makeTextColumn('UID', 'remoteUid'),
|
|
||||||
makeTextColumn('Назначена на скважину', 'realWell'),
|
|
||||||
makeTextColumn('Дата начала бурения', 'drillingStartDate', null, makeDateSorter('drillingStartDate')),
|
|
||||||
makeTextColumn('Часовой пояс', 'timeZoneId'),
|
|
||||||
makeTextColumn('Скважина', 'well'),
|
|
||||||
makeTextColumn('Куст', 'cluster'),
|
|
||||||
makeTextColumn('Месторождение', 'deposit'),
|
|
||||||
makeTextColumn('Заказчик', 'customer'),
|
|
||||||
makeTextColumn('Комментарий', 'comment'),
|
|
||||||
makeTextColumn('Версия HMI', 'hmiVersion'),
|
|
||||||
makeTextColumn('Версия САУБ', 'saubPlcVersion'),
|
|
||||||
makeTextColumn('Версия Спин Мастер', 'spinPlcVersion'),
|
|
||||||
]
|
|
||||||
|
|
||||||
export const TelemetryController = memo(() => {
|
|
||||||
const [telemetryData, setTelemetryData] = useState([])
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const telemetryData = arrayOrDefault(await AdminTelemetryService.getAll())
|
|
||||||
setTelemetryData(telemetryData.map((telemetry) => ({
|
|
||||||
...(telemetry?.info ?? []),
|
|
||||||
id: telemetry?.id,
|
|
||||||
remoteUid: telemetry?.remoteUid,
|
|
||||||
realWell: telemetry?.well?.caption,
|
|
||||||
})))
|
|
||||||
},
|
|
||||||
setIsLoading,
|
|
||||||
`Не удалось загрузить список телеметрии скважин`,
|
|
||||||
'Полученик списка телеметрии скважин'
|
|
||||||
), [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoaderPortal show={isLoading}>
|
|
||||||
<Table
|
|
||||||
size={'small'}
|
|
||||||
bordered
|
|
||||||
columns={columns}
|
|
||||||
dataSource={telemetryData}
|
|
||||||
pagination={defaultPagination}
|
|
||||||
tableName={'admin_telemetry_controller'}
|
|
||||||
/>
|
|
||||||
</LoaderPortal>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TelemetryController
|
|
@ -1,16 +1,12 @@
|
|||||||
import { Select } from 'antd'
|
import { Select } from 'antd'
|
||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
|
|
||||||
export const RoleTag = memo(({ roles, value, onChange }) => {
|
export const RoleTag = memo(({ roles, value, onChange }) => {
|
||||||
const [options, setOptions] = useState([])
|
const options = useMemo(() => roles.map((elm) => ({
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOptions(roles.map((elm) => ({
|
|
||||||
key: Date.now(),
|
key: Date.now(),
|
||||||
value: `${elm.caption}`,
|
value: `${elm.caption}`,
|
||||||
label: elm.caption
|
label: elm.caption
|
||||||
})))
|
})), [roles])
|
||||||
}, [roles])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { Button, Input, Tag } from 'antd'
|
import { Button, Input, Tag } from 'antd'
|
||||||
import { UserSwitchOutlined } from '@ant-design/icons'
|
import { UserSwitchOutlined } from '@ant-design/icons'
|
||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { BehaviorSubject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
|
import { BehaviorSubject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
makeColumn,
|
|
||||||
makeSelectColumn,
|
makeSelectColumn,
|
||||||
makeActionHandler,
|
makeActionHandler,
|
||||||
makeStringSorter,
|
|
||||||
makeNumericSorter,
|
makeNumericSorter,
|
||||||
defaultPagination
|
defaultPagination,
|
||||||
|
makeTextColumn
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import { RoleView } from '@components/views'
|
import { RoleView } from '@components/views'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
@ -38,7 +37,7 @@ export const UserController = memo(() => {
|
|||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const filteredUsers = users.filter((user) => user && [
|
const filteredUsers = users.filter((user) => user && (!searchValue || [
|
||||||
user.login ?? '',
|
user.login ?? '',
|
||||||
user.name ?? '',
|
user.name ?? '',
|
||||||
user.surname ?? '',
|
user.surname ?? '',
|
||||||
@ -47,7 +46,7 @@ export const UserController = memo(() => {
|
|||||||
user.phone ?? '',
|
user.phone ?? '',
|
||||||
user.position ?? '',
|
user.position ?? '',
|
||||||
user.company?.caption ?? '',
|
user.company?.caption ?? '',
|
||||||
].join(' ').toLowerCase().includes(searchValue))
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase())))
|
||||||
setFilteredUsers(filteredUsers)
|
setFilteredUsers(filteredUsers)
|
||||||
},
|
},
|
||||||
setIsSearching,
|
setIsSearching,
|
||||||
@ -106,8 +105,14 @@ export const UserController = memo(() => {
|
|||||||
const filters = makeTextFilters(users, ['surname', 'name', 'patronymic', 'email'])
|
const filters = makeTextFilters(users, ['surname', 'name', 'patronymic', 'email'])
|
||||||
const roleFilters = [{ text: 'Без роли', value: null }, ...roles.map((role) => ({ text: role.caption, value: role.caption }))]
|
const roleFilters = [{ text: 'Без роли', value: null }, ...roles.map((role) => ({ text: role.caption, value: role.caption }))]
|
||||||
|
|
||||||
|
const rolesRender = (item) => item?.map((elm) => (
|
||||||
|
<Tag key={elm} color={'blue'}>
|
||||||
|
<RoleView role={roles.find((role) => role.caption === elm)} />
|
||||||
|
</Tag>
|
||||||
|
)) ?? '-'
|
||||||
|
|
||||||
setColumns([
|
setColumns([
|
||||||
makeColumn('Логин', 'login', {
|
makeTextColumn('Логин', 'login', null, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: [
|
formItemRules: [
|
||||||
{ required: true },
|
{ required: true },
|
||||||
@ -121,59 +126,40 @@ export const UserController = memo(() => {
|
|||||||
// })
|
// })
|
||||||
// TODO: Для проверки уникальности логина необходимо исключить из выборки логин выбранного пользователя
|
// TODO: Для проверки уникальности логина необходимо исключить из выборки логин выбранного пользователя
|
||||||
],
|
],
|
||||||
sorter: makeStringSorter('login'),
|
|
||||||
}),
|
}),
|
||||||
makeColumn('Фамилия', 'surname', {
|
makeTextColumn('Фамилия', 'surname', filters.surname, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: [{ required: true }, ...nameRules],
|
formItemRules: [{ required: true }, ...nameRules],
|
||||||
sorter: makeStringSorter('surname'),
|
|
||||||
filters: filters.surname,
|
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('surname'),
|
onFilter: makeTextOnFilter('surname'),
|
||||||
}),
|
}),
|
||||||
makeColumn('Имя', 'name', {
|
makeTextColumn('Имя', 'name', filters.name, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: nameRules,
|
formItemRules: nameRules,
|
||||||
sorter: makeStringSorter('name'),
|
|
||||||
filters: filters.name,
|
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('name'),
|
onFilter: makeTextOnFilter('name'),
|
||||||
}),
|
}),
|
||||||
makeColumn('Отчество', 'patronymic', {
|
makeTextColumn('Отчество', 'patronymic', filters.partonymic, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: nameRules,
|
formItemRules: nameRules,
|
||||||
sorter: makeStringSorter('patronymic'),
|
|
||||||
filters: filters.patronymic,
|
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('patronymic'),
|
onFilter: makeTextOnFilter('patronymic'),
|
||||||
}),
|
}),
|
||||||
makeColumn('E-mail', 'email', {
|
makeTextColumn('E-mail', 'email', filters.email, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: [{ required: true }, ...emailRules],
|
formItemRules: [{ required: true }, ...emailRules],
|
||||||
sorter: makeStringSorter('email'),
|
|
||||||
filters: filters.email,
|
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
onFilter: makeTextOnFilter('email'),
|
onFilter: makeTextOnFilter('email'),
|
||||||
}),
|
}),
|
||||||
makeColumn('Номер телефона', 'phone', {
|
makeTextColumn('Номер телефона', 'phone', null, null, null, {
|
||||||
editable: true,
|
editable: true,
|
||||||
formItemRules: phoneRules,
|
formItemRules: phoneRules,
|
||||||
sorter: makeStringSorter('phone'),
|
|
||||||
}),
|
}),
|
||||||
makeColumn('Должность', 'position', {
|
makeTextColumn('Должность', 'position', null, null, null, { editable: true }),
|
||||||
editable: true,
|
makeTextColumn('Роли', 'roleNames', roleFilters, null, rolesRender, {
|
||||||
sorter: makeStringSorter('position'),
|
|
||||||
}),
|
|
||||||
makeColumn('Роли', 'roleNames', {
|
|
||||||
editable: true,
|
editable: true,
|
||||||
input: <RoleTag roles={roles} />,
|
input: <RoleTag roles={roles} />,
|
||||||
filters: roleFilters,
|
|
||||||
onFilter: makeArrayOnFilter('roleNames'),
|
onFilter: makeArrayOnFilter('roleNames'),
|
||||||
render: (item) => item?.map((elm) => (
|
|
||||||
<Tag key={elm} color={'blue'}>
|
|
||||||
<RoleView role={roles.find((role) => role.caption === elm)} />
|
|
||||||
</Tag>
|
|
||||||
)) ?? '-'
|
|
||||||
}),
|
}),
|
||||||
makeSelectColumn('Компания', 'idCompany', companies, '--', {
|
makeSelectColumn('Компания', 'idCompany', companies, '--', {
|
||||||
editable: true,
|
editable: true,
|
||||||
@ -186,12 +172,12 @@ export const UserController = memo(() => {
|
|||||||
'Получение списка компаний'
|
'Получение списка компаний'
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminUserService,
|
service: AdminUserService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateTable,
|
onComplete: updateTable,
|
||||||
}
|
}), [updateTable])
|
||||||
|
|
||||||
const onSearchTextChange = useCallback((e) => subject?.next(e?.target?.value), [subject])
|
const onSearchTextChange = useCallback((e) => subject?.next(e?.target?.value), [subject])
|
||||||
|
|
||||||
@ -200,7 +186,7 @@ export const UserController = memo(() => {
|
|||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
allowClear
|
allowClear
|
||||||
placeholder={'Поиск пользователей'}
|
placeholder={'Введите текст для поиска (по всем полям за исключением ролей)...'}
|
||||||
onChange={onSearchTextChange}
|
onChange={onSearchTextChange}
|
||||||
style={{ marginBottom: '15px' }}
|
style={{ marginBottom: '15px' }}
|
||||||
loading={isSearching}
|
loading={isSearching}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { defaultPagination, makeColumn, makeDateSorter, makeStringSorter, Table } from '@components/Table'
|
import { defaultPagination, makeColumn, makeDateSorter, makeStringSorter, Table } from '@components/Table'
|
||||||
import { RequestTrackerService } from '@api'
|
import { RequestTrackerService } from '@api'
|
||||||
@ -12,14 +12,22 @@ const columns = [
|
|||||||
makeColumn('Логин', 'login', { sorter: makeStringSorter('login') }),
|
makeColumn('Логин', 'login', { sorter: makeStringSorter('login') }),
|
||||||
makeColumn('IP', 'ip', { sorter: makeStringSorter('ip') }),
|
makeColumn('IP', 'ip', { sorter: makeStringSorter('ip') }),
|
||||||
makeColumn('Дата посещения', 'lastDate', {
|
makeColumn('Дата посещения', 'lastDate', {
|
||||||
render: (date) => formatDate(date, false, 'DD MMM YYYY, HH:mm:ss'),
|
render: (date) => formatDate(date, false),
|
||||||
sorter: makeDateSorter('lastDate'),
|
sorter: makeDateSorter('lastDate'),
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
export const VisitLog = memo(() => {
|
export const VisitLog = memo(() => {
|
||||||
const [logData, setLogData] = useState([])
|
const [logData, setLogData] = useState([])
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
|
const filteredLogData = useMemo(() => logData.filter((data) => data && (!searchValue || [
|
||||||
|
data.login ?? '',
|
||||||
|
data.ip ?? '',
|
||||||
|
data.lastDate ?? '',
|
||||||
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||||
|
), [logData, searchValue])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -27,23 +35,30 @@ export const VisitLog = memo(() => {
|
|||||||
logData.forEach((log) => log.key = `${log.login}${log.ip}`)
|
logData.forEach((log) => log.key = `${log.login}${log.ip}`)
|
||||||
setLogData(logData)
|
setLogData(logData)
|
||||||
},
|
},
|
||||||
setIsLoading,
|
setShowLoader,
|
||||||
`Не удалось загрузить список последних посещений пользователей`,
|
`Не удалось загрузить список последних посещений пользователей`,
|
||||||
'Получение списка последних посещений'
|
'Получение списка последних посещений'
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={isLoading}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<Table
|
<Table
|
||||||
size={'small'}
|
|
||||||
bordered
|
bordered
|
||||||
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={logData}
|
loading={showLoader}
|
||||||
|
dataSource={filteredLogData}
|
||||||
pagination={defaultPagination}
|
pagination={defaultPagination}
|
||||||
tableName={'visit_log'}
|
tableName={'visit_log'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import { memo } from 'react'
|
|
||||||
import { Select } from 'antd'
|
|
||||||
|
|
||||||
import { getTelemetryLabel } from '@components/views'
|
|
||||||
|
|
||||||
export const TelemetrySelect = memo(({ telemetry, value, onChange }) => (
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
value={value?.id}
|
|
||||||
onChange={(id) => onChange?.(telemetry.find((row) => row.id === id))}
|
|
||||||
dropdownClassName={'telemetry_select'}
|
|
||||||
>
|
|
||||||
{telemetry.map((row, i) => (
|
|
||||||
<Select.Option key={i} value={row.id}>
|
|
||||||
<span className={row?.info?.well ? 'telemetry_used' : 'telemetry_unused'}>
|
|
||||||
{getTelemetryLabel(row)}
|
|
||||||
</span>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
))
|
|
||||||
|
|
||||||
export default TelemetrySelect
|
|
@ -1,6 +1,6 @@
|
|||||||
import { Button } from 'antd'
|
import { Button, Input } from 'antd'
|
||||||
import { CopyOutlined } from '@ant-design/icons'
|
import { CopyOutlined } from '@ant-design/icons'
|
||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AdminClusterService,
|
AdminClusterService,
|
||||||
@ -19,16 +19,13 @@ import {
|
|||||||
defaultPagination,
|
defaultPagination,
|
||||||
makeTimezoneColumn,
|
makeTimezoneColumn,
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { TelemetryView, CompanyView } from '@components/views'
|
import { TelemetryView, CompanyView } from '@components/views'
|
||||||
|
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission } from '@utils/permissions'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
|
|
||||||
import { coordsFixed } from '../DepositController'
|
import { coordsFixed } from '../DepositController'
|
||||||
import TelemetrySelect from './TelemetrySelect'
|
|
||||||
|
|
||||||
import '@styles/admin.css'
|
|
||||||
|
|
||||||
const wellTypes = [
|
const wellTypes = [
|
||||||
{ value: 1, label: 'Наклонно-направленная' },
|
{ value: 1, label: 'Наклонно-направленная' },
|
||||||
@ -44,8 +41,17 @@ export const WellController = memo(() => {
|
|||||||
const [columns, setColumns] = useState([])
|
const [columns, setColumns] = useState([])
|
||||||
const [wells, setWells] = useState([])
|
const [wells, setWells] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
|
||||||
const updateTable = async () => invokeWebApiWrapperAsync(
|
const filteredWells = useMemo(() => wells.filter((well) => well && (!searchValue || [
|
||||||
|
well.caption ?? '',
|
||||||
|
well.latitude?.toString() ?? '',
|
||||||
|
well.longitude?.toString() ?? '',
|
||||||
|
].join(' ').toLowerCase().includes(searchValue.toLowerCase()))
|
||||||
|
), [wells, searchValue])
|
||||||
|
|
||||||
|
|
||||||
|
const updateTable = useCallback(async () => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const wells = await AdminWellService.getAll()
|
const wells = await AdminWellService.getAll()
|
||||||
setWells(arrayOrDefault(wells))
|
setWells(arrayOrDefault(wells))
|
||||||
@ -53,11 +59,11 @@ export const WellController = memo(() => {
|
|||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить список скважин`,
|
`Не удалось загрузить список скважин`,
|
||||||
'Получение списка скважин'
|
'Получение списка скважин'
|
||||||
)
|
), [])
|
||||||
|
|
||||||
const duplicateWell = (well) => {
|
const duplicateWell = useCallback((well) => {
|
||||||
// TODO: Метод дубликации скважины
|
// TODO: Метод дубликации скважины
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const addititonalButtons = memo((record, editingKey) => (
|
const addititonalButtons = memo((record, editingKey) => (
|
||||||
<Button
|
<Button
|
||||||
@ -112,22 +118,30 @@ export const WellController = memo(() => {
|
|||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось загрузить список кустов`,
|
`Не удалось загрузить список кустов`,
|
||||||
'Получение списка кустов'
|
'Получение списка кустов'
|
||||||
), [])
|
), [updateTable])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: AdminWellService,
|
service: AdminWellService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateTable
|
onComplete: updateTable
|
||||||
}
|
}), [updateTable])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
|
<Input.Search
|
||||||
|
style={{ margin: '15px 0' }}
|
||||||
|
placeholder={'Введите текст для поиска (по полям: Название, Долгота, Широта)...'}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
value={searchValue}
|
||||||
|
loading={showLoader}
|
||||||
|
/>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
size={'small'}
|
|
||||||
bordered
|
bordered
|
||||||
|
size={'small'}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={wells}
|
loading={showLoader}
|
||||||
|
dataSource={filteredWells}
|
||||||
pagination={defaultPagination}
|
pagination={defaultPagination}
|
||||||
onRowAdd={hasPermission('AdminWell.edit') && makeActionHandler('insert', handlerProps, recordParser, 'Добавление скважины')}
|
onRowAdd={hasPermission('AdminWell.edit') && makeActionHandler('insert', handlerProps, recordParser, 'Добавление скважины')}
|
||||||
onRowEdit={hasPermission('AdminWell.edit') && makeActionHandler('update', handlerProps, recordParser, 'Редактирование скважины')}
|
onRowEdit={hasPermission('AdminWell.edit') && makeActionHandler('update', handlerProps, recordParser, 'Редактирование скважины')}
|
||||||
@ -136,7 +150,7 @@ export const WellController = memo(() => {
|
|||||||
buttonsWidth={95}
|
buttonsWidth={95}
|
||||||
tableName={'admin_well_controller'}
|
tableName={'admin_well_controller'}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Layout, Menu } from 'antd'
|
import { Layout, Menu } from 'antd'
|
||||||
import { lazy, Suspense } from 'react'
|
import { lazy, memo, Suspense } from 'react'
|
||||||
import { Switch, useParams } from 'react-router-dom'
|
import { Switch, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private'
|
import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private'
|
||||||
@ -14,12 +14,12 @@ const WellController = lazy(() => import( './WellController'))
|
|||||||
const RoleController = lazy(() => import( './RoleController'))
|
const RoleController = lazy(() => import( './RoleController'))
|
||||||
const CompanyTypeController = lazy(() => import('./CompanyTypeController'))
|
const CompanyTypeController = lazy(() => import('./CompanyTypeController'))
|
||||||
const PermissionController = lazy(() => import( './PermissionController'))
|
const PermissionController = lazy(() => import( './PermissionController'))
|
||||||
const TelemetryController = lazy(() => import( './TelemetryController'))
|
const TelemetrySection = lazy(() => import( './Telemetry'))
|
||||||
const VisitLog = lazy(() => import( './VisitLog'))
|
const VisitLog = lazy(() => import( './VisitLog'))
|
||||||
|
|
||||||
const rootPath = '/admin'
|
const rootPath = '/admin'
|
||||||
|
|
||||||
export const AdminPanel = () => {
|
export const AdminPanel = memo(() => {
|
||||||
const { tab } = useParams()
|
const { tab } = useParams()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -33,7 +33,7 @@ export const AdminPanel = () => {
|
|||||||
<PrivateMenuItem.Link root={rootPath} key={'company_type'} path={'company_type'} title={'Типы компаний' } />
|
<PrivateMenuItem.Link root={rootPath} key={'company_type'} path={'company_type'} title={'Типы компаний' } />
|
||||||
<PrivateMenuItem.Link root={rootPath} key={'role' } path={'role' } title={'Роли' } />
|
<PrivateMenuItem.Link root={rootPath} key={'role' } path={'role' } title={'Роли' } />
|
||||||
<PrivateMenuItem.Link root={rootPath} key={'permission' } path={'permission' } title={'Разрешения' } />
|
<PrivateMenuItem.Link root={rootPath} key={'permission' } path={'permission' } title={'Разрешения' } />
|
||||||
<PrivateMenuItem.Link root={rootPath} key={'telemetry' } path={'telemetry' } title={'Телеметрии' } />
|
<PrivateMenuItem.Link root={rootPath} key={'telemetry' } path={'telemetry' } title={'Телеметрия' } />
|
||||||
<PrivateMenuItem.Link root={rootPath} key={'visit_log' } path={'visit_log' } title={'Журнал посещений'} />
|
<PrivateMenuItem.Link root={rootPath} key={'visit_log' } path={'visit_log' } title={'Журнал посещений'} />
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
@ -46,10 +46,10 @@ export const AdminPanel = () => {
|
|||||||
<PrivateRoute path={`${rootPath}/well` } component={ WellController} />
|
<PrivateRoute path={`${rootPath}/well` } component={ WellController} />
|
||||||
<PrivateRoute path={`${rootPath}/user` } component={ UserController} />
|
<PrivateRoute path={`${rootPath}/user` } component={ UserController} />
|
||||||
<PrivateRoute path={`${rootPath}/company` } component={ CompanyController} />
|
<PrivateRoute path={`${rootPath}/company` } component={ CompanyController} />
|
||||||
<PrivateRoute path={`${rootPath}/company_type`} component={CompanyTypeController} />
|
<PrivateRoute path={`${rootPath}/company_type` } component={CompanyTypeController} />
|
||||||
<PrivateRoute path={`${rootPath}/role` } component={ RoleController} />
|
<PrivateRoute path={`${rootPath}/role` } component={ RoleController} />
|
||||||
<PrivateRoute path={`${rootPath}/permission` } component={ PermissionController} />
|
<PrivateRoute path={`${rootPath}/permission` } component={ PermissionController} />
|
||||||
<PrivateRoute path={`${rootPath}/telemetry` } component={ TelemetryController} />
|
<PrivateRoute path={`${rootPath}/telemetry/:tab?`} component={TelemetrySection} />
|
||||||
<PrivateRoute path={`${rootPath}/visit_log` } component={VisitLog} />
|
<PrivateRoute path={`${rootPath}/visit_log` } component={VisitLog} />
|
||||||
<PrivateDefaultRoute urls={[
|
<PrivateDefaultRoute urls={[
|
||||||
`${rootPath}/deposit`,
|
`${rootPath}/deposit`,
|
||||||
@ -69,6 +69,6 @@ export const AdminPanel = () => {
|
|||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default AdminPanel
|
export default AdminPanel
|
||||||
|
@ -2,8 +2,8 @@ import { Table as RawTable, Typography } from 'antd'
|
|||||||
import { Fragment, memo, useCallback, useEffect, useState } from 'react'
|
import { Fragment, memo, useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { WellSelector } from '@components/WellSelector'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { WellSelector } from '@components/selectors/WellSelector'
|
||||||
import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
||||||
import { OperationStatService, WellOperationService } from '@api'
|
import { OperationStatService, WellOperationService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
@ -18,10 +18,17 @@ const { Cell, Row } = Summary
|
|||||||
const numericRender = makeNumericRender()
|
const numericRender = makeNumericRender()
|
||||||
const speedNumericRender = (section) => numericRender(section?.speed)
|
const speedNumericRender = (section) => numericRender(section?.speed)
|
||||||
|
|
||||||
|
const makeSectionSorter = (key, name) => (a, b) => (a?.[key]?.[name] ?? 0) - (b?.[key]?.[name] ?? 0)
|
||||||
export const makeSectionColumn = (title, key, { speedRender } = {}) => makeGroupColumn(title, [
|
export const makeSectionColumn = (title, key, { speedRender } = {}) => makeGroupColumn(title, [
|
||||||
makeNumericColumn('Проходка', key, null, null, (section => numericRender(section?.depth)), 100),
|
makeNumericColumn('Проходка', key, null, null, (section => numericRender(section?.depth)), 100, {
|
||||||
makeNumericColumn('Время', key, null, null, (section => numericRender(section?.time)), 100),
|
sorter: makeSectionSorter(key, 'depth'),
|
||||||
makeNumericColumn((<>V<sub>рейсовая</sub></>), key, null, null, speedRender ?? speedNumericRender, 100),
|
}),
|
||||||
|
makeNumericColumn('Время', key, null, null, (section => numericRender(section?.time)), 100, {
|
||||||
|
sorter: makeSectionSorter(key, 'time'),
|
||||||
|
}),
|
||||||
|
makeNumericColumn((<>V<sub>рейсовая</sub></>), key, null, null, speedRender ?? speedNumericRender, 100, {
|
||||||
|
sorter: makeSectionSorter(key, 'speed'),
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
export const defaultColumns = [
|
export const defaultColumns = [
|
||||||
@ -90,21 +97,30 @@ export const Statistics = memo(({ idWell }) => {
|
|||||||
async () => {
|
async () => {
|
||||||
const types = await WellOperationService.getSectionTypes(idWell)
|
const types = await WellOperationService.getSectionTypes(idWell)
|
||||||
setSectionTypes(Object.entries(types))
|
setSectionTypes(Object.entries(types))
|
||||||
|
},
|
||||||
|
setIsPageLoading,
|
||||||
|
`Не удалось получить типы секции`,
|
||||||
|
`Получение списка возможных секций`,
|
||||||
|
), [idWell])
|
||||||
|
|
||||||
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const filteredSections = avgData?.length > 0 ? sectionTypes.filter(([id, _]) => avgData.some((row) => `section_${id}` in row)) : sectionTypes
|
||||||
|
|
||||||
setAvgColumns([
|
setAvgColumns([
|
||||||
...defaultColumns,
|
...defaultColumns,
|
||||||
...Object.entries(types).map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
||||||
])
|
])
|
||||||
setCmpColumns([
|
setCmpColumns([
|
||||||
...defaultColumns,
|
...defaultColumns,
|
||||||
...Object.entries(types).map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
||||||
speedRender: cmpSpeedRender(`section_${id}`)
|
speedRender: cmpSpeedRender(`section_${id}`)
|
||||||
}))
|
}))
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
setIsPageLoading,
|
setIsPageLoading,
|
||||||
`Не удалось получить типы секции`,
|
'Не удалось установить необходимые столбцы'
|
||||||
`Получение списка возможных секций`,
|
), [sectionTypes, avgData, cmpSpeedRender])
|
||||||
), [idWell, cmpSpeedRender])
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
import { useState, useEffect, memo, useCallback } from 'react'
|
import { useState, useEffect, memo, useCallback, useMemo } from 'react'
|
||||||
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
||||||
import { Table, Tag, Button, Badge, Divider, Modal, Row, Col, Popconfirm } from 'antd'
|
import { Table, Tag, Button, Badge, Divider, Modal, Row, Col, Popconfirm } from 'antd'
|
||||||
|
|
||||||
@ -29,9 +29,7 @@ const filtersSectionsType = []
|
|||||||
const DAY_IN_MS = 1000 * 60 * 60 * 24
|
const DAY_IN_MS = 1000 * 60 * 60 * 24
|
||||||
|
|
||||||
export const WellCompositeSections = memo(({ idWell, statsWells, selectedSections }) => {
|
export const WellCompositeSections = memo(({ idWell, statsWells, selectedSections }) => {
|
||||||
const [rows, setRows] = useState([])
|
|
||||||
const [params, setParams] = useState([])
|
const [params, setParams] = useState([])
|
||||||
const [paramsColumns, setParamsColumns] = useState([])
|
|
||||||
const [selectedWells, setSelectedWells] = useState([])
|
const [selectedWells, setSelectedWells] = useState([])
|
||||||
const [wellOperations, setWellOperations] = useState([])
|
const [wellOperations, setWellOperations] = useState([])
|
||||||
const [selectedWellsKeys, setSelectedWellsKeys] = useState([])
|
const [selectedWellsKeys, setSelectedWellsKeys] = useState([])
|
||||||
@ -44,21 +42,9 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
|||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
useEffect(() => (async() => setParamsColumns(await getColumns(idWell)))(), [idWell])
|
const paramsColumns = useMemo(async() => await getColumns(idWell), [idWell])
|
||||||
|
|
||||||
useEffect(() => {
|
const rows = useMemo(() => {
|
||||||
if (isOpsModalVisible || selectedWellId <= 0) return
|
|
||||||
invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const { operations } = await getOperations(selectedWellId)
|
|
||||||
setWellOperations(operations)
|
|
||||||
},
|
|
||||||
setShowLoader,
|
|
||||||
`Не удалось загрузить операции по скважине "${selectedWellId}"`,
|
|
||||||
)
|
|
||||||
}, [selectedWellId, isOpsModalVisible])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const rows = []
|
const rows = []
|
||||||
statsWells?.forEach((well) => {
|
statsWells?.forEach((well) => {
|
||||||
well.sections?.forEach((section) => {
|
well.sections?.forEach((section) => {
|
||||||
@ -113,9 +99,21 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
|||||||
'nonProductiveTimeFact',
|
'nonProductiveTimeFact',
|
||||||
])
|
])
|
||||||
|
|
||||||
setRows(rows)
|
return rows
|
||||||
}, [statsWells])
|
}, [statsWells])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpsModalVisible || selectedWellId <= 0) return
|
||||||
|
invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const { operations } = await getOperations(selectedWellId)
|
||||||
|
setWellOperations(operations)
|
||||||
|
},
|
||||||
|
setShowLoader,
|
||||||
|
`Не удалось загрузить операции по скважине "${selectedWellId}"`,
|
||||||
|
)
|
||||||
|
}, [selectedWellId, isOpsModalVisible])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selected = rows.filter((row) => selectedSections.some(section => (
|
const selected = rows.filter((row) => selectedSections.some(section => (
|
||||||
section.idWellSrc === row.id && section.idWellSectionType === row.sectionId
|
section.idWellSrc === row.id && section.idWellSectionType === row.sectionId
|
||||||
@ -125,7 +123,7 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
|||||||
setSelectedWellsKeys(selected.map((row) => row.key))
|
setSelectedWellsKeys(selected.map((row) => row.key))
|
||||||
}, [rows, selectedSections])
|
}, [rows, selectedSections])
|
||||||
|
|
||||||
const columns = [
|
const columns = useMemo(() => [
|
||||||
makeTextColumn('скв №', 'caption', null, null,
|
makeTextColumn('скв №', 'caption', null, null,
|
||||||
(text, item) => <Link to={{ pathname: `/well/${item?.id}`, state: { from: location.pathname }}}>{text ?? '-'}</Link>
|
(text, item) => <Link to={{ pathname: `/well/${item?.id}`, state: { from: location.pathname }}}>{text ?? '-'}</Link>
|
||||||
),
|
),
|
||||||
@ -171,9 +169,9 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
|||||||
</Tag>
|
</Tag>
|
||||||
)) ?? '-',
|
)) ?? '-',
|
||||||
},
|
},
|
||||||
]
|
], [location.pathname])
|
||||||
|
|
||||||
const rowSelection = hasPermission('WellOperation.edit') && {
|
const rowSelection = useMemo(() => hasPermission('WellOperation.edit') && {
|
||||||
selectedRowKeys: selectedWellsKeys,
|
selectedRowKeys: selectedWellsKeys,
|
||||||
onChange: (keys, items) => invokeWebApiWrapperAsync(
|
onChange: (keys, items) => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -187,7 +185,7 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
|||||||
`Не удалось сохранить изменения выбранных секций для композитной скважины "${idWell}"`,
|
`Не удалось сохранить изменения выбранных секций для композитной скважины "${idWell}"`,
|
||||||
'Изменение выбранных секций скважины'
|
'Изменение выбранных секций скважины'
|
||||||
)
|
)
|
||||||
}
|
}, [idWell, selectedWellsKeys])
|
||||||
|
|
||||||
const onParamButtonClick = useCallback(() => invokeWebApiWrapperAsync(
|
const onParamButtonClick = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from '@api'
|
} from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import WellSelector from '@components/WellSelector'
|
import WellSelector from '@components/selectors/WellSelector'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { PrivateDefaultRoute, PrivateMenuItemLink, PrivateRoute } from '@components/Private'
|
import { PrivateDefaultRoute, PrivateMenuItemLink, PrivateRoute } from '@components/Private'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { memo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { Layout, Menu } from 'antd'
|
import { Layout, Menu } from 'antd'
|
||||||
import { Switch, useParams } from 'react-router-dom'
|
import { Switch, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ import Statistics from './Statistics'
|
|||||||
|
|
||||||
export const Analytics = memo(({ idWell }) => {
|
export const Analytics = memo(({ idWell }) => {
|
||||||
const { tab } = useParams()
|
const { tab } = useParams()
|
||||||
const rootPath = `/well/${idWell}/analytics`
|
const rootPath = useMemo(() => `/well/${idWell}/analytics`, [idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
|
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
import { Grid, GridItem } from '@components/Grid'
|
||||||
import { Column } from '@components/charts/Column'
|
import { Column } from '@components/charts/Column'
|
||||||
|
|
||||||
export const ArchiveColumn = ({ lineGroup, data, interval, style, headerHeight, yStart }) => {
|
export const ArchiveColumn = memo(({ lineGroup, data, interval, style, headerHeight, yStart }) => {
|
||||||
const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
|
const lgws = useMemo(() => lineGroup.filter(cfg => !cfg.isShape), [lineGroup])
|
||||||
const [pv, setPV] = useState([])
|
const pv = useMemo(() => lgws.filter(line => line.showLabels).map(line => ({
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const lgws = lineGroup.filter(cfg => !cfg.isShape)
|
|
||||||
setLineGroupWithoutShapes(lgws)
|
|
||||||
setPV(lgws.filter(line => line.showLabels).map(line => ({
|
|
||||||
color: line.color,
|
color: line.color,
|
||||||
label: line.label
|
label: line.label
|
||||||
})))
|
})), [lgws])
|
||||||
}, [lineGroup])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
@ -26,13 +19,13 @@ export const ArchiveColumn = ({ lineGroup, data, interval, style, headerHeight,
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Column
|
<Column
|
||||||
data={data}
|
data={data}
|
||||||
lineGroup={lineGroupWithoutShapes}
|
lineGroup={lgws}
|
||||||
interval={interval}
|
interval={interval}
|
||||||
yDisplay={false}
|
yDisplay={false}
|
||||||
yStart={yStart}
|
yStart={yStart}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default ArchiveColumn
|
export default ArchiveColumn
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
|
|
||||||
import { Grid, GridItem } from '@components/Grid'
|
import { Grid, GridItem } from '@components/Grid'
|
||||||
|
|
||||||
@ -41,13 +41,9 @@ export const cutData = (data, beginDate, endDate) => {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ArchiveDisplay = ({data, startDate, interval, onWheel}) => {
|
export const ArchiveDisplay = memo(({data, startDate, interval, onWheel}) => {
|
||||||
const [chartData, setChartData] = useState([])
|
const endDate = useMemo(() => new Date(+startDate + interval), [startDate, interval])
|
||||||
|
const chartData = useMemo(() => cutData(data, startDate, endDate), [data, startDate, endDate])
|
||||||
useEffect(() => {
|
|
||||||
const endDate = new Date(+startDate + interval)
|
|
||||||
setChartData(cutData(data, startDate, endDate))
|
|
||||||
}, [data, startDate, interval])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid onWheel={onWheel}>
|
<Grid onWheel={onWheel}>
|
||||||
@ -65,6 +61,6 @@ export const ArchiveDisplay = ({data, startDate, interval, onWheel}) => {
|
|||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default ArchiveDisplay
|
export default ArchiveDisplay
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useState, useEffect, memo } from 'react'
|
import { useState, useEffect, memo, useCallback } from 'react'
|
||||||
|
|
||||||
import { Flex } from '@components/Grid'
|
import { Flex } from '@components/Grid'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/PeriodPicker'
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
import { TelemetryDataSaubService } from '@api'
|
import { TelemetryDataSaubService } from '@api'
|
||||||
|
|
||||||
import { normalizeData } from '@pages/TelemetryView'
|
import { normalizeData } from '@pages/TelemetryView'
|
||||||
@ -69,7 +69,7 @@ export const Archive = memo(({ idWell }) => {
|
|||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [loaded, setLoaded] = useState(null)
|
const [loaded, setLoaded] = useState(null)
|
||||||
|
|
||||||
const onGraphWheel = (e) => {
|
const onGraphWheel = useCallback((e) => {
|
||||||
if (loaded && dateLimit.from && dateLimit.to) {
|
if (loaded && dateLimit.from && dateLimit.to) {
|
||||||
setStartDate((prevStartDate) => {
|
setStartDate((prevStartDate) => {
|
||||||
const offset = e.deltaY * chartInterval * WHEEL_SENSITIVITY
|
const offset = e.deltaY * chartInterval * WHEEL_SENSITIVITY
|
||||||
@ -79,14 +79,15 @@ export const Archive = memo(({ idWell }) => {
|
|||||||
return new Date(Math.max(firstPossibleDate, Math.min(nextStartDate, lastPossibleDate)))
|
return new Date(Math.max(firstPossibleDate, Math.min(nextStartDate, lastPossibleDate)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}, [loaded, dateLimit, chartInterval])
|
||||||
|
|
||||||
const isDateDisabled = (date) => {
|
const isDateDisabled = useCallback((date) => {
|
||||||
if (!date) return false
|
if (!date) return false
|
||||||
const dt = new Date(date).setHours(0, 0, 0, 0)
|
const dt = new Date(date).setHours(0, 0, 0, 0)
|
||||||
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||||
}
|
}, [dateLimit])
|
||||||
const isDateTimeDisabled = (date) => ({
|
|
||||||
|
const isDateTimeDisabled = useCallback((date) => ({
|
||||||
disabledHours: () => range(0, 24).filter(h => {
|
disabledHours: () => range(0, 24).filter(h => {
|
||||||
if (!date) return false
|
if (!date) return false
|
||||||
const dt = +new Date(date).setHours(h)
|
const dt = +new Date(date).setHours(h)
|
||||||
@ -102,7 +103,7 @@ export const Archive = memo(({ idWell }) => {
|
|||||||
const dt = +new Date(date).setSeconds(s)
|
const dt = +new Date(date).setSeconds(s)
|
||||||
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||||
})
|
})
|
||||||
})
|
}), [dateLimit])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
import { useState, useEffect, memo } from 'react'
|
import { useState, useEffect, memo, useMemo } from 'react'
|
||||||
import { Tag, Button, Modal } from 'antd'
|
import { Tag, Button, Modal } from 'antd'
|
||||||
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ export const ClusterWells = memo(({ statsWells }) => {
|
|||||||
setTableData(data)
|
setTableData(data)
|
||||||
}, [statsWells])
|
}, [statsWells])
|
||||||
|
|
||||||
const columns = [
|
const columns = useMemo(() => [
|
||||||
makeTextColumn('скв №', 'caption', null, null,
|
makeTextColumn('скв №', 'caption', null, null,
|
||||||
(_, item) => (
|
(_, item) => (
|
||||||
<Link to={{ pathname: `/well/${item.id}`, state: { from: location.pathname }}} style={{display: 'flex', alignItems: 'center'}}>
|
<Link to={{ pathname: `/well/${item.id}`, state: { from: location.pathname }}} style={{display: 'flex', alignItems: 'center'}}>
|
||||||
@ -159,7 +159,7 @@ export const ClusterWells = memo(({ statsWells }) => {
|
|||||||
</Tag>
|
</Tag>
|
||||||
)) ?? '-',
|
)) ?? '-',
|
||||||
},
|
},
|
||||||
]
|
], [location.pathname])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
|
import { memo, useMemo } from 'react'
|
||||||
import { Table } from 'antd'
|
import { Table } from 'antd'
|
||||||
|
|
||||||
import { makeTextColumn, makeNumericColumnPlanFact } from '@components/Table'
|
import { makeTextColumn, makeNumericColumnPlanFact } from '@components/Table'
|
||||||
import { getPrecision } from '@utils/functions'
|
import { getPrecision } from '@utils/functions'
|
||||||
|
|
||||||
export const WellOperationsTable = ({ wellOperations }) => {
|
const columns = [
|
||||||
const columns = [
|
makeTextColumn('Конструкция секции', 'sectionType'),
|
||||||
makeTextColumn('Конструкция секции','sectionType'),
|
makeTextColumn('Операция', 'operationName'),
|
||||||
makeTextColumn('Операция','operationName'),
|
|
||||||
makeNumericColumnPlanFact('Глубина забоя', 'depth', null, null, getPrecision),
|
makeNumericColumnPlanFact('Глубина забоя', 'depth', null, null, getPrecision),
|
||||||
makeNumericColumnPlanFact('Часы', 'durationHours', null, null, getPrecision),
|
makeNumericColumnPlanFact('Часы', 'durationHours', null, null, getPrecision),
|
||||||
makeNumericColumnPlanFact('Комментарий', 'comment', null, null, (text) => text ?? '-')
|
makeNumericColumnPlanFact('Комментарий', 'comment', null, null, (text) => text ?? '-')
|
||||||
]
|
]
|
||||||
|
|
||||||
const operations = wellOperations?.map(el => ({
|
export const WellOperationsTable = memo(({ wellOperations }) => {
|
||||||
|
const operations = useMemo(() => wellOperations?.map(el => ({
|
||||||
key: el.plan?.id ?? el.fact.id,
|
key: el.plan?.id ?? el.fact.id,
|
||||||
sectionType: el.plan?.wellSectionTypeName ?? el.fact?.wellSectionTypeName,
|
sectionType: el.plan?.wellSectionTypeName ?? el.fact?.wellSectionTypeName,
|
||||||
operationName: `${el.plan?.categoryName ?? el.fact?.categoryName ?? ''} ${' '}
|
operationName: `${el.plan?.categoryName ?? el.fact?.categoryName ?? ''} ${' '}
|
||||||
@ -23,7 +24,7 @@ export const WellOperationsTable = ({ wellOperations }) => {
|
|||||||
durationHoursFact: el.fact?.durationHours,
|
durationHoursFact: el.fact?.durationHours,
|
||||||
commentPlan: el.plan?.comment ?? '-',
|
commentPlan: el.plan?.comment ?? '-',
|
||||||
commentFact: el.fact?.comment ?? '-'
|
commentFact: el.fact?.comment ?? '-'
|
||||||
}))
|
})), [wellOperations])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
@ -36,6 +37,6 @@ export const WellOperationsTable = ({ wellOperations }) => {
|
|||||||
tableName={'well_operations'}
|
tableName={'well_operations'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default WellOperationsTable
|
export default WellOperationsTable
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, memo } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
@ -8,7 +8,7 @@ import { invokeWebApiWrapperAsync } from '@components/factory'
|
|||||||
|
|
||||||
import ClusterWells from './ClusterWells'
|
import ClusterWells from './ClusterWells'
|
||||||
|
|
||||||
export const Cluster = () => {
|
export const Cluster = memo(() => {
|
||||||
const { idCluster } = useParams()
|
const { idCluster } = useParams()
|
||||||
const [data, setData] = useState([])
|
const [data, setData] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -28,6 +28,6 @@ export const Cluster = () => {
|
|||||||
<ClusterWells statsWells={data} />
|
<ClusterWells statsWells={data} />
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export default Cluster
|
export default Cluster
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useMemo, useCallback } from 'react'
|
||||||
import { DatePicker, Button, Input } from 'antd'
|
import { DatePicker, Button, Input } from 'antd'
|
||||||
|
|
||||||
import { FileService } from '@api'
|
import { FileService } from '@api'
|
||||||
import { hasPermission } from '@utils/permissions'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { UploadForm } from '@components/UploadForm'
|
import { UploadForm } from '@components/UploadForm'
|
||||||
import { CompanyView, UserView } from '@components/views'
|
import { CompanyView, UserView } from '@components/views'
|
||||||
import { EditableTable, makePaginationObject } from '@components/Table'
|
import { EditableTable, makeColumn, makeDateColumn, makeNumericColumn, makePaginationObject } from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync, downloadFile, formatBytes } from '@components/factory'
|
import { invokeWebApiWrapperAsync, downloadFile, formatBytes } from '@components/factory'
|
||||||
import { formatDate } from '@utils'
|
import { hasPermission } from '@utils/permissions'
|
||||||
|
|
||||||
const pageSize = 12
|
const pageSize = 12
|
||||||
const { RangePicker } = DatePicker
|
const { RangePicker } = DatePicker
|
||||||
@ -24,26 +23,11 @@ const columns = [
|
|||||||
{name}
|
{name}
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
}, {
|
},
|
||||||
title: 'Дата загрузки',
|
makeDateColumn('Дата загрузки', 'uploadDate'),
|
||||||
key: 'uploadDate',
|
makeNumericColumn('Размер', 'size', null, null, formatBytes),
|
||||||
dataIndex: 'uploadDate',
|
makeColumn('Автор', 'author', { render: item => <UserView user={item}/> }),
|
||||||
render: item => formatDate(item, false, 'DD MMM YYYY, HH:mm:ss'),
|
makeColumn('Компания', 'company', { render: (_, record) => <CompanyView company={record?.author?.company}/> })
|
||||||
}, {
|
|
||||||
title: 'Размер',
|
|
||||||
key: 'size',
|
|
||||||
dataIndex: 'size',
|
|
||||||
render: item => formatBytes(item)
|
|
||||||
}, {
|
|
||||||
title: 'Автор',
|
|
||||||
key: 'author',
|
|
||||||
dataIndex: 'author',
|
|
||||||
render: item => <UserView user={item}/>
|
|
||||||
}, {
|
|
||||||
title: 'Компания',
|
|
||||||
key: 'company',
|
|
||||||
render: (_, record) => <CompanyView company={record?.author?.company}/>
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, customColumns, beforeTable, onChange, tableName }) => {
|
export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, customColumns, beforeTable, onChange, tableName }) => {
|
||||||
@ -55,21 +39,13 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
|||||||
const [files, setFiles] = useState([])
|
const [files, setFiles] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const uploadUrl = `/api/well/${idWell}/files/?idCategory=${idCategory}`
|
const uploadUrl = useMemo(() => `/api/well/${idWell}/files/?idCategory=${idCategory}`, [idWell, idCategory])
|
||||||
|
|
||||||
const handleUploadComplete = () => update()
|
const mergedColumns = useMemo(() => [...columns, ...(customColumns ?? [])], [customColumns])
|
||||||
|
const companies = useMemo(() => [...new Set(files.map(file => file.company))].filter(company => company), [files])
|
||||||
|
const filenames = useMemo(() => [...new Set(files.map(file => file.name))].filter(name => name), [files])
|
||||||
|
|
||||||
const handleFileDelete = async (file) => {
|
const update = useCallback(() => {
|
||||||
await FileService.delete(idWell, file.id)
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
|
|
||||||
const hanleCompanySearch = (value, _) => setFilterCompanyName(value)
|
|
||||||
const hanleFileNameSearch = (value, _) => setFilterFileName(value)
|
|
||||||
|
|
||||||
const mergedColumns = [...columns, ...(customColumns ?? [])]
|
|
||||||
|
|
||||||
const update = () => {
|
|
||||||
let begin = null
|
let begin = null
|
||||||
let end = null
|
let end = null
|
||||||
if (filterDataRange?.length > 1) {
|
if (filterDataRange?.length > 1) {
|
||||||
@ -101,13 +77,15 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
|||||||
`Не удалось загрузить файлы по скважине "${idWell}"`,
|
`Не удалось загрузить файлы по скважине "${idWell}"`,
|
||||||
'Загрузка файла по скважине'
|
'Загрузка файла по скважине'
|
||||||
)
|
)
|
||||||
}
|
}, [filterCompanyName, filterDataRange, filterFileName, idCategory, idWell, page])
|
||||||
|
|
||||||
useEffect(update, [idWell, idCategory, page, filterDataRange, filterCompanyName, filterFileName])
|
useEffect(update, [update])
|
||||||
useEffect(() => onChange?.(files), [files, onChange])
|
useEffect(() => onChange?.(files), [files, onChange])
|
||||||
|
|
||||||
const companies = [...new Set(files.map(file => file.company))].filter(company => company)
|
const handleFileDelete = useMemo(() => hasPermission(`File.edit${idCategory}`) && (async (file) => {
|
||||||
const filenames = [...new Set(files.map(file => file.name))].filter(name => name)
|
await FileService.delete(idWell, file.id)
|
||||||
|
update()
|
||||||
|
}), [idWell, idCategory, update])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
@ -124,7 +102,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
|||||||
<Search
|
<Search
|
||||||
list={'listCompanies'}
|
list={'listCompanies'}
|
||||||
placeholder={'Фильтр по компании'}
|
placeholder={'Фильтр по компании'}
|
||||||
onSearch={hanleCompanySearch}
|
onSearch={setFilterCompanyName}
|
||||||
/>
|
/>
|
||||||
<datalist id={'listCompanies'}>
|
<datalist id={'listCompanies'}>
|
||||||
{companies.map((company) => (
|
{companies.map((company) => (
|
||||||
@ -138,7 +116,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
|||||||
<Search
|
<Search
|
||||||
list={'listFileNames'}
|
list={'listFileNames'}
|
||||||
placeholder={'Фильтр по имени файла'}
|
placeholder={'Фильтр по имени файла'}
|
||||||
onSearch={hanleFileNameSearch}
|
onSearch={setFilterFileName}
|
||||||
/>
|
/>
|
||||||
<datalist id={'listFileNames'}>
|
<datalist id={'listFileNames'}>
|
||||||
{filenames.map((name) => (
|
{filenames.map((name) => (
|
||||||
@ -154,7 +132,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
|||||||
url={uploadUrl}
|
url={uploadUrl}
|
||||||
accept={accept}
|
accept={accept}
|
||||||
onUploadStart={() => setShowLoader(true)}
|
onUploadStart={() => setShowLoader(true)}
|
||||||
onUploadComplete={handleUploadComplete}
|
onUploadComplete={update}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -169,9 +147,9 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
|||||||
pagination={{
|
pagination={{
|
||||||
...pagination,
|
...pagination,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
onChange: (page) => setPage(page),
|
onChange: setPage,
|
||||||
}}
|
}}
|
||||||
onRowDelete={hasPermission(`File.edit${idCategory}`) && handleFileDelete}
|
onRowDelete={handleFileDelete}
|
||||||
rowKey={(record) => record.id}
|
rowKey={(record) => record.id}
|
||||||
tableName={tableName ?? `file_${idCategory}`}
|
tableName={tableName ?? `file_${idCategory}`}
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { memo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import { Layout, Menu } from 'antd'
|
import { Layout, Menu } from 'antd'
|
||||||
import { FolderOutlined } from '@ant-design/icons'
|
import { FolderOutlined } from '@ant-design/icons'
|
||||||
import { Switch, useParams } from 'react-router-dom'
|
import { Switch, useParams } from 'react-router-dom'
|
||||||
@ -25,7 +25,7 @@ export const documentCategories = [
|
|||||||
|
|
||||||
export const MenuDocuments = memo(({ idWell }) => {
|
export const MenuDocuments = memo(({ idWell }) => {
|
||||||
const { category } = useParams()
|
const { category } = useParams()
|
||||||
const root = `/well/${idWell}/document`
|
const root = useMemo(() => `/well/${idWell}/document`, [idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -2,7 +2,7 @@ import { Form, Select } from 'antd'
|
|||||||
import { FileAddOutlined } from '@ant-design/icons'
|
import { FileAddOutlined } from '@ant-design/icons'
|
||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import Poprompt from '@components/Poprompt'
|
import Poprompt from '@components/selectors/Poprompt'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DrillingProgramService } from '@api'
|
import { DrillingProgramService } from '@api'
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Input, Modal, Radio } from 'antd'
|
import { Input, Modal, Radio } from 'antd'
|
||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { BehaviorSubject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
|
import { BehaviorSubject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
|
||||||
|
|
||||||
import { UserView } from '@components/views'
|
import { UserView } from '@components/views'
|
||||||
@ -113,7 +113,7 @@ export const CategoryEditor = memo(({ idWell, visible, category, onClosed }) =>
|
|||||||
`Изменение статуса пользователя`
|
`Изменение статуса пользователя`
|
||||||
), [users, idWell, category.idFileCategory])
|
), [users, idWell, category.idFileCategory])
|
||||||
|
|
||||||
const userColumns = [
|
const userColumns = useMemo(() => [
|
||||||
makeColumn('Пользователь', 'user', {
|
makeColumn('Пользователь', 'user', {
|
||||||
sorter: (a, b) => (a?.user?.surname && b?.user?.surname) ? a.user.surname.localeCompare(b.user.surname) : 0,
|
sorter: (a, b) => (a?.user?.surname && b?.user?.surname) ? a.user.surname.localeCompare(b.user.surname) : 0,
|
||||||
render: (user) => <UserView user={user} />,
|
render: (user) => <UserView user={user} />,
|
||||||
@ -129,7 +129,7 @@ export const CategoryEditor = memo(({ idWell, visible, category, onClosed }) =>
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
]
|
], [changeUserStatus])
|
||||||
|
|
||||||
const onSearchTextChange = useCallback((e) => subject?.next(e?.target?.value), [subject])
|
const onSearchTextChange = useCallback((e) => subject?.next(e?.target?.value), [subject])
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { memo, useCallback, useState } from 'react'
|
import { memo, useCallback, useMemo, useState } from 'react'
|
||||||
import { Button, Input, Popconfirm, Form } from 'antd'
|
import { Button, Input, Popconfirm, Form } from 'antd'
|
||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
@ -6,11 +6,11 @@ import {
|
|||||||
TableOutlined,
|
TableOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
|
|
||||||
import Poprompt from '@components/Poprompt'
|
|
||||||
import { UserView } from '@components/views'
|
import { UserView } from '@components/views'
|
||||||
import UploadForm from '@components/UploadForm'
|
import UploadForm from '@components/UploadForm'
|
||||||
import DownloadLink from '@components/DownloadLink'
|
import DownloadLink from '@components/DownloadLink'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import Poprompt from '@components/selectors/Poprompt'
|
||||||
import { formatBytes, invokeWebApiWrapperAsync, notify } from '@components/factory'
|
import { formatBytes, invokeWebApiWrapperAsync, notify } from '@components/factory'
|
||||||
import { DrillingProgramService } from '@api'
|
import { DrillingProgramService } from '@api'
|
||||||
import { formatDate } from '@utils'
|
import { formatDate } from '@utils'
|
||||||
@ -44,7 +44,7 @@ export const CategoryRender = memo(({ idWell, partData, onUpdate, onEdit, onHist
|
|||||||
file // Информация о файле
|
file // Информация о файле
|
||||||
} = partData ?? {}
|
} = partData ?? {}
|
||||||
|
|
||||||
const uploadUrl = `/api/well/${idWell}/drillingProgram/part/${idFileCategory}`
|
const uploadUrl = useMemo(() => `/api/well/${idWell}/drillingProgram/part/${idFileCategory}`, [idWell, idFileCategory])
|
||||||
|
|
||||||
const [isUploading, setIsUploading] = useState(false)
|
const [isUploading, setIsUploading] = useState(false)
|
||||||
const [isDeleting, setIsDeleting] = useState(false)
|
const [isDeleting, setIsDeleting] = useState(false)
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
WarningOutlined,
|
WarningOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { memo, useCallback, useEffect, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory'
|
import { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
@ -52,11 +52,11 @@ export const DrillingProgram = memo(({ idWell }) => {
|
|||||||
parts,
|
parts,
|
||||||
program,
|
program,
|
||||||
error,
|
error,
|
||||||
} = data
|
} = useMemo(() => data, [data])
|
||||||
|
|
||||||
const stateId = idState ?? idStateUnknown
|
const stateId = useMemo(() => idState ?? idStateUnknown, [idState])
|
||||||
const state = stateString[stateId]
|
const state = useMemo(() => stateString[stateId], [stateId])
|
||||||
const StateIcon = state.icon
|
const StateIcon = useMemo(() => state.icon, [state?.icon])
|
||||||
|
|
||||||
const updateData = useCallback(async () => await invokeWebApiWrapperAsync(
|
const updateData = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -76,15 +76,15 @@ export const DrillingProgram = memo(({ idWell }) => {
|
|||||||
|
|
||||||
useEffect(() => updateData(), [updateData])
|
useEffect(() => updateData(), [updateData])
|
||||||
|
|
||||||
const onCategoryEdit = (catId) => {
|
const onCategoryEdit = useCallback((catId) => {
|
||||||
setSelectedCategory(catId)
|
setSelectedCategory(catId)
|
||||||
setEditorVisible(!!catId)
|
setEditorVisible(!!catId)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const onCategoryHistory = (catId) => {
|
const onCategoryHistory = useCallback((catId) => {
|
||||||
setSelectedCategory(catId)
|
setSelectedCategory(catId)
|
||||||
setHistoryVisible(!!catId)
|
setHistoryVisible(!!catId)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const onEditorClosed = useCallback(() => {
|
const onEditorClosed = useCallback(() => {
|
||||||
setEditorVisible(false)
|
setEditorVisible(false)
|
||||||
|
@ -13,9 +13,10 @@ import '@styles/index.css'
|
|||||||
import Logo from '@images/Logo'
|
import Logo from '@images/Logo'
|
||||||
|
|
||||||
export const Login = memo(() => {
|
export const Login = memo(() => {
|
||||||
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
|
||||||
|
|
||||||
const handleLogin = useCallback((formData) => invokeWebApiWrapperAsync(
|
const handleLogin = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Modal } from 'antd'
|
import { Modal } from 'antd'
|
||||||
import { memo, useEffect, useState } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
|
|
||||||
import { Table } from '@components/Table'
|
import { Table } from '@components/Table'
|
||||||
import { formatDate } from '@utils'
|
import { formatDate } from '@utils'
|
||||||
@ -18,21 +18,18 @@ const dateColumn = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const InclinometryTable = memo(({ group, visible, onClose }) => {
|
export const InclinometryTable = memo(({ group, visible, onClose }) => {
|
||||||
const [tableColumns, setTableColumns] = useState([])
|
const tableColumns = useMemo(() => [
|
||||||
const [tableData, setTableData] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => setTableColumns([
|
|
||||||
dateColumn,
|
dateColumn,
|
||||||
...(group?.columns?.map((column) => ({
|
...(group?.columns?.map((column) => ({
|
||||||
...column,
|
...column,
|
||||||
title: v(column.title)
|
title: v(column.title)
|
||||||
})) ?? [])
|
})) ?? [])
|
||||||
]), [group?.columns])
|
], [group?.columns])
|
||||||
|
|
||||||
useEffect(() => setTableData(group?.values?.map(row => ({
|
const tableData = useMemo(() => group?.values?.map(row => ({
|
||||||
date: row.timestamp,
|
date: row.timestamp,
|
||||||
...row.data
|
...row.data
|
||||||
}))), [group?.values])
|
})), [group?.values])
|
||||||
|
|
||||||
return !group?.columns ? null : (
|
return !group?.columns ? null : (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, memo } from 'react'
|
import { useState, useEffect, memo, useMemo, useCallback } from 'react'
|
||||||
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
|
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
|
||||||
import {
|
import {
|
||||||
CheckSquareOutlined,
|
CheckSquareOutlined,
|
||||||
@ -23,23 +23,20 @@ import '@styles/measure.css'
|
|||||||
const createEditingColumns = (cols, renderDelegate) =>
|
const createEditingColumns = (cols, renderDelegate) =>
|
||||||
cols.map(col => ({ render: renderDelegate, ...col }))
|
cols.map(col => ({ render: renderDelegate, ...col }))
|
||||||
|
|
||||||
|
const disabled = !hasPermission('Measure.edit')
|
||||||
|
|
||||||
export const MeasureTable = memo(({ idWell, group, updateMeasuresFunc, additionalButtons }) => {
|
export const MeasureTable = memo(({ idWell, group, updateMeasuresFunc, additionalButtons }) => {
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [displayedValues, setDisplayedValues] = useState({})
|
const [displayedValues, setDisplayedValues] = useState({})
|
||||||
const [editingColumns, setEditingColumns] = useState(group.columns)
|
const [editingColumns, setEditingColumns] = useState(group.columns)
|
||||||
const [isTableEditing, setIsTableEditing] = useState(false)
|
const [isTableEditing, setIsTableEditing] = useState(false)
|
||||||
const [editingActionName, setEditingActionName] = useState('')
|
const [editingActionName, setEditingActionName] = useState('')
|
||||||
const [data, setData] = useState([])
|
|
||||||
|
|
||||||
const [measuresForm] = Form.useForm()
|
const [measuresForm] = Form.useForm()
|
||||||
|
|
||||||
useEffect(() => {
|
const data = useMemo(() => group?.values?.length > 0 ? group.values : [group?.defaultValue], [group?.defaultValue, group?.values])
|
||||||
let data = [group.defaultValue]
|
|
||||||
if (group?.values?.length > 0)
|
useEffect(() => setDisplayedValues(data.at(-1)), [data])
|
||||||
data = group.values
|
|
||||||
setData(data)
|
|
||||||
setDisplayedValues(data.at(-1))
|
|
||||||
}, [group.defaultValue, group.values])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const switchableColumns = createEditingColumns(
|
const switchableColumns = createEditingColumns(
|
||||||
@ -64,17 +61,15 @@ export const MeasureTable = memo(({ idWell, group, updateMeasuresFunc, additiona
|
|||||||
`Не удалось удалить запись ${displayedValues.id} для скважины "${idWell}"`,
|
`Не удалось удалить запись ${displayedValues.id} для скважины "${idWell}"`,
|
||||||
'Удаление записи для скважины'
|
'Удаление записи для скважины'
|
||||||
)
|
)
|
||||||
|
const editingDisabled = useMemo(() => disabled || !!displayedValues?.isDefaultData, [displayedValues?.isDefaultData])
|
||||||
|
const deleteDisabled = useMemo(() => !hasPermission('Measure.delete') || !!displayedValues?.isDefaultData, [displayedValues?.isDefaultData])
|
||||||
|
|
||||||
const disabled = !hasPermission('Measure.edit')
|
const editTable = useCallback((action) => {
|
||||||
const editingDisabled = disabled || !!displayedValues?.isDefaultData
|
|
||||||
const deleteDisabled = !hasPermission('Measure.delete') || !!displayedValues?.isDefaultData
|
|
||||||
|
|
||||||
const editTable = (action) => {
|
|
||||||
setEditingActionName(action)
|
setEditingActionName(action)
|
||||||
setIsTableEditing(true)
|
setIsTableEditing(true)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const handleSubmitMeasuresForm = async (formData) => await invokeWebApiWrapperAsync(
|
const handleSubmitMeasuresForm = useCallback(async (formData) => await invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
measuresForm.validateFields()
|
measuresForm.validateFields()
|
||||||
|
|
||||||
@ -99,7 +94,7 @@ export const MeasureTable = memo(({ idWell, group, updateMeasuresFunc, additiona
|
|||||||
setShowLoader,
|
setShowLoader,
|
||||||
`Не удалось добавить/изменить запись для скаважины "${idWell}"`,
|
`Не удалось добавить/изменить запись для скаважины "${idWell}"`,
|
||||||
'Добавление/изменение записи по скважине'
|
'Добавление/изменение записи по скважине'
|
||||||
)
|
), [displayedValues?.id, displayedValues?.timestamp, editingActionName, group.idCategory, idWell, measuresForm, updateMeasuresFunc])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useState, useEffect, memo } from 'react'
|
import { useState, useEffect, memo, useCallback } from 'react'
|
||||||
import { Table, Select, DatePicker, Input } from 'antd'
|
import { Table, Select, DatePicker, Input } from 'antd'
|
||||||
|
|
||||||
import { MessageService } from '@api'
|
import { MessageService } from '@api'
|
||||||
@ -65,6 +65,8 @@ const filterOptions = [
|
|||||||
{ value: 3, label: 'Информация' },
|
{ value: 3, label: 'Информация' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const children = filterOptions.map((line) => <Option key={line.value}>{line.label}</Option>)
|
||||||
|
|
||||||
// Данные для таблицы
|
// Данные для таблицы
|
||||||
export const Messages = memo(({ idWell }) => {
|
export const Messages = memo(({ idWell }) => {
|
||||||
const [messages, setMessages] = useState([])
|
const [messages, setMessages] = useState([])
|
||||||
@ -75,9 +77,7 @@ export const Messages = memo(({ idWell }) => {
|
|||||||
const [searchString, setSearchString] = useState('')
|
const [searchString, setSearchString] = useState('')
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const children = filterOptions.map((line) => <Option key={line.value}>{line.label}</Option>)
|
const onChangeSearchString = useCallback((message) => setSearchString(message.length > 2 ? message : ''), [])
|
||||||
|
|
||||||
const onChangeSearchString = (message) => setSearchString(message.length > 2 ? message : '')
|
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'moment/locale/ru'
|
import 'moment/locale/ru'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useState, useEffect, memo } from 'react'
|
import { useState, useEffect, memo, useCallback } from 'react'
|
||||||
import { Radio, Button, Select, notification } from 'antd'
|
import { Radio, Button, Select, notification } from 'antd'
|
||||||
|
|
||||||
import { ReportService } from '@api'
|
import { ReportService } from '@api'
|
||||||
@ -43,7 +43,7 @@ export const Report = memo(({ idWell }) => {
|
|||||||
const [pagesCount, setPagesCount] = useState(0)
|
const [pagesCount, setPagesCount] = useState(0)
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
|
|
||||||
const handleReportCreation = async () => await invokeWebApiWrapperAsync(
|
const handleReportCreation = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const taskId = await ReportService.createReport(
|
const taskId = await ReportService.createReport(
|
||||||
idWell,
|
idWell,
|
||||||
@ -81,9 +81,11 @@ export const Report = memo(({ idWell }) => {
|
|||||||
${filterDateRange[0].format(dateTimeFormat)} по
|
${filterDateRange[0].format(dateTimeFormat)} по
|
||||||
${filterDateRange[1].format(dateTimeFormat)}`,
|
${filterDateRange[1].format(dateTimeFormat)}`,
|
||||||
'Создание отчёта по скважине'
|
'Создание отчёта по скважине'
|
||||||
)
|
), [filterDateRange, format, idWell, step])
|
||||||
|
|
||||||
const disabledDate = (current) => !current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
|
const disabledDate = useCallback((current) =>
|
||||||
|
!current.isBetween(aviableDateRange[0], aviableDateRange[1], 'seconds', '[]')
|
||||||
|
, [aviableDateRange])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -6,7 +6,7 @@ import { Grid, GridItem } from '@components/Grid'
|
|||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { makeNumericRender, EditableTable } from '@components/Table'
|
import { makeNumericRender, EditableTable } from '@components/Table'
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/PeriodPicker'
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
|
|
||||||
export const SetpointSender = ({ idWell, onClose, visible, setpointNames }) => {
|
export const SetpointSender = ({ idWell, onClose, visible, setpointNames }) => {
|
||||||
const [expirePeriod, setExpirePeriod] = useState(defaultPeriod)
|
const [expirePeriod, setExpirePeriod] = useState(defaultPeriod)
|
||||||
|
@ -12,7 +12,7 @@ import { makeDateSorter } from '@components/Table'
|
|||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { Grid, GridItem, Flex } from '@components/Grid'
|
import { Grid, GridItem, Flex } from '@components/Grid'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { PeriodPicker, defaultPeriod } from '@components/PeriodPicker'
|
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission } from '@utils/permissions'
|
||||||
import { Subscribe } from '@services/signalr'
|
import { Subscribe } from '@services/signalr'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { memo } from 'react'
|
import { memo, useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
FundViewOutlined,
|
FundViewOutlined,
|
||||||
@ -30,7 +30,7 @@ const { Content } = Layout
|
|||||||
|
|
||||||
export const Well = memo(() => {
|
export const Well = memo(() => {
|
||||||
const { idWell, tab } = useParams()
|
const { idWell, tab } = useParams()
|
||||||
const rootPath = `/well/${idWell}`
|
const rootPath = useMemo(() => `/well/${idWell}`, [idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
@ -12,8 +12,8 @@ const style = { margin: 4 }
|
|||||||
export const ImportExportBar = memo(({ idWell, onImported, disabled }) => {
|
export const ImportExportBar = memo(({ idWell, onImported, disabled }) => {
|
||||||
const [isImportModalVisible, setIsImportModalVisible] = useState(false)
|
const [isImportModalVisible, setIsImportModalVisible] = useState(false)
|
||||||
|
|
||||||
const downloadTemplate = async () => download(`/api/well/${idWell}/wellOperations/template`)
|
const downloadTemplate = async () => await download(`/api/well/${idWell}/wellOperations/template`)
|
||||||
const downloadExport = async () => download(`/api/well/${idWell}/wellOperations/export`)
|
const downloadExport = async () => await download(`/api/well/${idWell}/wellOperations/export`)
|
||||||
|
|
||||||
const onDone = () => {
|
const onDone = () => {
|
||||||
setIsImportModalVisible(false)
|
setIsImportModalVisible(false)
|
||||||
|
@ -1,183 +0,0 @@
|
|||||||
import { Switch } from 'antd'
|
|
||||||
import { memo, useState, useRef, useEffect } from 'react'
|
|
||||||
|
|
||||||
import {
|
|
||||||
Chart,
|
|
||||||
TimeScale,
|
|
||||||
LinearScale,
|
|
||||||
Legend,
|
|
||||||
LineController,
|
|
||||||
PointElement,
|
|
||||||
LineElement
|
|
||||||
} from 'chart.js'
|
|
||||||
import 'chartjs-adapter-moment'
|
|
||||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { getOperations } from '@utils/functions'
|
|
||||||
|
|
||||||
Chart.register(TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin)
|
|
||||||
|
|
||||||
const scaleTypes = {
|
|
||||||
day: {
|
|
||||||
min: 0,
|
|
||||||
type: 'linear',
|
|
||||||
display: true,
|
|
||||||
title: { display: false, text: '' },
|
|
||||||
ticks: { stepSize: 1 }
|
|
||||||
},
|
|
||||||
date: {
|
|
||||||
display: true,
|
|
||||||
title: { display: true },
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'hour',
|
|
||||||
displayFormats: { 'hour': 'MM.DD' }
|
|
||||||
},
|
|
||||||
grid: { drawTicks: true },
|
|
||||||
ticks: {
|
|
||||||
stepSize: 3,
|
|
||||||
major: { enabled: true },
|
|
||||||
z: 1,
|
|
||||||
display: true,
|
|
||||||
textStrokeColor: '#fff',
|
|
||||||
textStrokeWidth: 2,
|
|
||||||
color: '#000',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
responsive: true,
|
|
||||||
aspectRatio: 2.6,
|
|
||||||
interaction: {
|
|
||||||
intersect: false,
|
|
||||||
mode: 'index',
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: scaleTypes.day,
|
|
||||||
y: {
|
|
||||||
type: 'linear',
|
|
||||||
position: 'top',
|
|
||||||
reverse: true,
|
|
||||||
display: true,
|
|
||||||
title: {
|
|
||||||
display: false,
|
|
||||||
text: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parsing: {
|
|
||||||
xAxisKey: 'day',
|
|
||||||
yAxisKey: 'depth',
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
point: {
|
|
||||||
radius: 1.7,
|
|
||||||
//backgroundColor:'#aaa',
|
|
||||||
//pointStyle:'triangle',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: true,
|
|
||||||
},
|
|
||||||
datalabels: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
callbacks: {
|
|
||||||
label: (tooltipItem) => tooltipItem.yLabel,
|
|
||||||
},
|
|
||||||
position: 'nearest',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({
|
|
||||||
label,
|
|
||||||
data,
|
|
||||||
backgroundColor: color,
|
|
||||||
borderColor: color,
|
|
||||||
borderWidth,
|
|
||||||
borderDash,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Tvd = memo(({ idWell, title }) => {
|
|
||||||
const [operations, setOperations] = useState([])
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [xLabel, setXLabel] = useState('day')
|
|
||||||
const [chart, setChart] = useState()
|
|
||||||
|
|
||||||
const chartRef = useRef(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const operations = await getOperations(idWell)
|
|
||||||
setOperations(operations)
|
|
||||||
},
|
|
||||||
setIsLoading,
|
|
||||||
`Не удалось загрузить операции по скважине "${idWell}"`,
|
|
||||||
'Получение списка опервций по скважине'
|
|
||||||
)
|
|
||||||
}, [idWell])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const data = {
|
|
||||||
datasets: [
|
|
||||||
makeDataset(operations?.fact, 'Факт', '#0A0'),
|
|
||||||
makeDataset(operations?.predict, 'Прогноз', 'purple', 1, [7, 3]),
|
|
||||||
makeDataset(operations?.plan, 'План', '#C004', 4),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chartRef.current && !chart) {
|
|
||||||
const thisOptions = {}
|
|
||||||
Object.assign(thisOptions, defaultOptions)
|
|
||||||
thisOptions.scales.x = scaleTypes[xLabel]
|
|
||||||
thisOptions.parsing.xAxisKey = xLabel
|
|
||||||
|
|
||||||
const newChart = new Chart(chartRef.current, {
|
|
||||||
type: 'line',
|
|
||||||
plugins: [ChartDataLabels],
|
|
||||||
options: thisOptions,
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
setChart(newChart)
|
|
||||||
|
|
||||||
return () => chart?.destroy()
|
|
||||||
} else {
|
|
||||||
chart.data = data
|
|
||||||
chart.options.scales.x = scaleTypes[xLabel]
|
|
||||||
chart.options.parsing.xAxisKey = xLabel
|
|
||||||
chart.update()
|
|
||||||
}
|
|
||||||
}, [chart, operations, xLabel])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'container'}>
|
|
||||||
<div>
|
|
||||||
{title || (
|
|
||||||
<h2 className={'mt-20px'}>График Глубина-день</h2>
|
|
||||||
)}
|
|
||||||
<LoaderPortal show={isLoading}>
|
|
||||||
<canvas ref={chartRef} />
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', width: '100%' }}>
|
|
||||||
<Switch
|
|
||||||
checkedChildren={'Дата'}
|
|
||||||
unCheckedChildren={'Дни со старта'}
|
|
||||||
loading={isLoading}
|
|
||||||
onChange={(checked) => setXLabel(checked ? 'date' : 'day')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</LoaderPortal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default Tvd
|
|
67
src/pages/WellOperations/Tvd/AdditionalTables.jsx
Normal file
67
src/pages/WellOperations/Tvd/AdditionalTables.jsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Descriptions } from 'antd'
|
||||||
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { makeNumericRender } from '@components/Table'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { formatDate, fractionalSum } from '@utils/datetime'
|
||||||
|
|
||||||
|
import '@styles/tvd.less'
|
||||||
|
|
||||||
|
const { Item } = Descriptions
|
||||||
|
|
||||||
|
const calcEndDate = (saubData) => {
|
||||||
|
if (!Array.isArray(saubData) || saubData.length <= 0) return [null, null]
|
||||||
|
const lastElm = saubData.at(-1)
|
||||||
|
return [saubData[0]?.date, fractionalSum(lastElm?.date, lastElm?.nptHours, 'hour')]
|
||||||
|
}
|
||||||
|
|
||||||
|
const numericRender = makeNumericRender(2)
|
||||||
|
const printDate = (date) => formatDate(date) ?? '-'
|
||||||
|
|
||||||
|
export const AdditionalTables = memo(({ operations, xLabel, setIsLoading }) => {
|
||||||
|
const [additionalData, setAdditionalData] = useState({})
|
||||||
|
|
||||||
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => {
|
||||||
|
const [factStartDate, factEndDate] = calcEndDate(operations.fact)
|
||||||
|
const [planStartDate, planEndDate] = calcEndDate(operations.plan)
|
||||||
|
const [predictStartDate, predictEndDate] = calcEndDate(operations.predict)
|
||||||
|
|
||||||
|
const last = predictEndDate ?? factEndDate
|
||||||
|
setAdditionalData({
|
||||||
|
lag: (+new Date(last) - +new Date(planEndDate)) / 86400_000,
|
||||||
|
endDate: last,
|
||||||
|
factStartDate,
|
||||||
|
factEndDate,
|
||||||
|
planStartDate,
|
||||||
|
planEndDate,
|
||||||
|
predictStartDate,
|
||||||
|
predictEndDate,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setIsLoading,
|
||||||
|
'Не удалось высчитать дополнительные данные'
|
||||||
|
), [operations, setIsLoading])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={'tvd-tr-table'}>
|
||||||
|
<Descriptions bordered column={1} size={'small'} style={{ backgroundColor: 'white' }}>
|
||||||
|
<Item label={'Дата завершения'}>{printDate(additionalData.endDate)}</Item>
|
||||||
|
<Item label={'Отставание (сут)'}>{numericRender(additionalData.lag)}</Item>
|
||||||
|
</Descriptions>
|
||||||
|
</div>
|
||||||
|
<div className={'tvd-bl-table'} style={{ bottom: xLabel === 'day' ? '35px' : '85px' }}>
|
||||||
|
<Descriptions bordered column={1} size={'small'} style={{ backgroundColor: 'white' }}>
|
||||||
|
<Item label={'Отставание (сут)'}>{numericRender(additionalData.lag)}</Item>
|
||||||
|
<Item label={'Начало цикла (план)'}>{printDate(additionalData.planStartDate)}</Item>
|
||||||
|
<Item label={'Начало цикла (факт)'}>{printDate(additionalData.factStartDate)}</Item>
|
||||||
|
<Item label={'Окончание цикла (план)'}>{printDate(additionalData.planEndDate)}</Item>
|
||||||
|
<Item label={'Окончание цикла (факт)'}>{printDate(additionalData.factEndDate)}</Item>
|
||||||
|
</Descriptions>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AdditionalTables
|
29
src/pages/WellOperations/Tvd/NetGraphExport.jsx
Normal file
29
src/pages/WellOperations/Tvd/NetGraphExport.jsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { memo, useCallback, useState } from 'react'
|
||||||
|
import { ExportOutlined } from '@ant-design/icons'
|
||||||
|
import { Button } from 'antd'
|
||||||
|
|
||||||
|
import { download, invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
|
||||||
|
export const NetGraphExport = memo(({ idWell, ...other }) => {
|
||||||
|
const [isFileExporting, setIsFileExporting] = useState(false)
|
||||||
|
|
||||||
|
const onExport = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => await download(`/api/well/${idWell}/wellOperations/scheduleReport`),
|
||||||
|
setIsFileExporting,
|
||||||
|
'Не удалось загрузить файл'
|
||||||
|
), [idWell])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
icon={<ExportOutlined />}
|
||||||
|
loading={isFileExporting}
|
||||||
|
onClick={onExport}
|
||||||
|
style={{ marginRight: '5px' }}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
Сетевой график
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default NetGraphExport
|
68
src/pages/WellOperations/Tvd/NptTable.jsx
Normal file
68
src/pages/WellOperations/Tvd/NptTable.jsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
import { InputNumber } from 'antd'
|
||||||
|
import { FilterOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { makeDateColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
||||||
|
|
||||||
|
import '@styles/tvd.less'
|
||||||
|
|
||||||
|
export const columns = [
|
||||||
|
makeTextColumn('Конструкция секции', 'wellSectionTypeName', null, null, null, { width: 140 }),
|
||||||
|
makeNumericColumn('Глубина', 'depth', null, null, null, 80),
|
||||||
|
makeDateColumn('Дата начала', 'date', false, undefined, { width: 90 }),
|
||||||
|
makeNumericColumn('Длительность (ч)', 'durationHours', null, null, makeNumericRender(2), 120),
|
||||||
|
makeTextColumn('Доп. инфо', 'categoryInfo', null, null, null),
|
||||||
|
makeTextColumn('Комментарий', 'comment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const NptTable = memo(({ operations }) => {
|
||||||
|
const [filterValue, setFilterValue] = useState(0)
|
||||||
|
const [npt, setNPT] = useState([])
|
||||||
|
const [filteredNPT, setFilteredNPT] = useState([])
|
||||||
|
const [isTableLoading, setIsTableLoading] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => setNPT(operations?.filter((row) => row?.isNPT) ?? []),
|
||||||
|
setIsTableLoading,
|
||||||
|
'Не удалось получить список НПВ'
|
||||||
|
), [operations])
|
||||||
|
|
||||||
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => setFilteredNPT(npt.filter((row) => !filterValue || row.durationHours >= filterValue)),
|
||||||
|
setIsTableLoading,
|
||||||
|
'Не удалось отфильтровать НПВ по времени'
|
||||||
|
), [npt, filterValue])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'tvd-right'}>
|
||||||
|
<div className={'tvd-npt-filter'}>
|
||||||
|
<FilterOutlined />
|
||||||
|
<span>Фильтр время НПВ ≥</span>
|
||||||
|
<InputNumber
|
||||||
|
step={0.5}
|
||||||
|
max={10000}
|
||||||
|
min={0}
|
||||||
|
defaultValue={0}
|
||||||
|
onChange={(value) => setFilterValue(value ?? 0)}
|
||||||
|
value={filterValue}
|
||||||
|
/>
|
||||||
|
<span>ч.</span>
|
||||||
|
</div>
|
||||||
|
<LoaderPortal show={isTableLoading}>
|
||||||
|
<Table
|
||||||
|
bordered
|
||||||
|
size={'small'}
|
||||||
|
dataSource={filteredNPT}
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
tableName={'tvd_npt'}
|
||||||
|
scroll={{ y: '60vh', scrollToFirstRowOnChange: true }}
|
||||||
|
/>
|
||||||
|
</LoaderPortal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default NptTable
|
226
src/pages/WellOperations/Tvd/index.jsx
Normal file
226
src/pages/WellOperations/Tvd/index.jsx
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { useHistory } from 'react-router-dom'
|
||||||
|
import { memo, useState, useRef, useEffect, useCallback } from 'react'
|
||||||
|
import { DoubleLeftOutlined, DoubleRightOutlined } from '@ant-design/icons'
|
||||||
|
import { Switch, Button } from 'antd'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Chart,
|
||||||
|
TimeScale,
|
||||||
|
LinearScale,
|
||||||
|
Legend,
|
||||||
|
LineController,
|
||||||
|
PointElement,
|
||||||
|
LineElement
|
||||||
|
} from 'chart.js'
|
||||||
|
import 'chartjs-adapter-moment'
|
||||||
|
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||||
|
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||||
|
|
||||||
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
|
import { formatDate, fractionalSum } from '@utils/datetime'
|
||||||
|
import { getOperations } from '@utils/functions'
|
||||||
|
|
||||||
|
import NptTable from './NptTable'
|
||||||
|
import NetGraphExport from './NetGraphExport'
|
||||||
|
import AdditionalTables from './AdditionalTables'
|
||||||
|
|
||||||
|
import '@styles/index.css'
|
||||||
|
import '@styles/tvd.less'
|
||||||
|
|
||||||
|
Chart.register(
|
||||||
|
TimeScale,
|
||||||
|
LinearScale,
|
||||||
|
LineController,
|
||||||
|
LineElement,
|
||||||
|
PointElement,
|
||||||
|
Legend,
|
||||||
|
ChartDataLabels,
|
||||||
|
zoomPlugin
|
||||||
|
)
|
||||||
|
|
||||||
|
const numericRender = (value) => Number.isFinite(value) ? (+value).toFixed(2) : '-'
|
||||||
|
|
||||||
|
const scaleTypes = {
|
||||||
|
day: {
|
||||||
|
min: 0,
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
title: { display: false, text: '' },
|
||||||
|
ticks: { stepSize: 1 }
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
display: true,
|
||||||
|
title: { display: true },
|
||||||
|
type: 'time',
|
||||||
|
time: { unit: 'day', displayFormats: { day: 'DD.MM.YYYY' } },
|
||||||
|
grid: { drawTicks: true },
|
||||||
|
ticks: {
|
||||||
|
stepSize: 3,
|
||||||
|
major: { enabled: true },
|
||||||
|
z: 1,
|
||||||
|
display: true,
|
||||||
|
textStrokeColor: '#fff',
|
||||||
|
textStrokeWidth: 2,
|
||||||
|
color: '#000',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
aspectRatio: false,
|
||||||
|
interaction: { intersect: false, mode: 'point' },
|
||||||
|
scales: {
|
||||||
|
x: scaleTypes.day,
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'top',
|
||||||
|
reverse: true,
|
||||||
|
display: true,
|
||||||
|
title: { display: false, text: '' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parsing: { xAxisKey: 'day', yAxisKey: 'depth' },
|
||||||
|
elements: { point: { radius: 1.7 } },
|
||||||
|
plugins: {
|
||||||
|
legend: { display: true },
|
||||||
|
datalabels: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
position: 'nearest',
|
||||||
|
callbacks: {
|
||||||
|
title: (items) => [
|
||||||
|
`Дата: ${formatDate(items[0].raw?.date) ?? '-'}`,
|
||||||
|
`День с начала бурения: ${parseInt(items[0].raw?.day)}`,
|
||||||
|
],
|
||||||
|
afterTitle: (items) => `Глубина: ${numericRender(items[0].raw?.depth)}`,
|
||||||
|
label: (item) => [
|
||||||
|
item.raw.wellSectionTypeName + ': ' + item.raw.categoryName,
|
||||||
|
`Длительность (ч): ${item.raw.nptHours}`
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeDataset = (data, label, color, borderWidth = 1.5, borderDash) => ({
|
||||||
|
label,
|
||||||
|
data,
|
||||||
|
backgroundColor: color,
|
||||||
|
borderColor: color,
|
||||||
|
borderWidth,
|
||||||
|
borderDash,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Tvd = memo(({ idWell, title }) => {
|
||||||
|
const [chart, setChart] = useState()
|
||||||
|
const [xLabel, setXLabel] = useState('day')
|
||||||
|
const [operations, setOperations] = useState({})
|
||||||
|
const [tableVisible, setTableVisible] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const chartRef = useRef(null)
|
||||||
|
const history = useHistory()
|
||||||
|
|
||||||
|
const onPointClick = useCallback((e) => {
|
||||||
|
const points = e?.chart?.tooltip?.dataPoints
|
||||||
|
if (!points || !(points.length > 0)) return
|
||||||
|
|
||||||
|
const datasetId = points.find((p) => p.datasetIndex !== 1)?.datasetIndex
|
||||||
|
if (typeof datasetId === 'undefined') return
|
||||||
|
|
||||||
|
const datasetName = datasetId === 2 ? 'plan' : 'fact'
|
||||||
|
const ids = points.filter((p) => p?.raw?.id).map((p) => p.raw.id).join(',')
|
||||||
|
history.push(`/well/${idWell}/operations/${datasetName}/?selectedId=${ids}`)
|
||||||
|
}, [idWell, history])
|
||||||
|
|
||||||
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
|
async () => setOperations(await getOperations(idWell)),
|
||||||
|
setIsLoading,
|
||||||
|
`Не удалось загрузить операции по скважине "${idWell}"`,
|
||||||
|
'Получение списка опервций по скважине'
|
||||||
|
), [idWell])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const withoutNpt = []
|
||||||
|
operations?.fact?.forEach((row) => {
|
||||||
|
if (row?.isNPT !== false) return
|
||||||
|
const nptH = +(row.nptHours ?? 0)
|
||||||
|
withoutNpt.push({
|
||||||
|
...row,
|
||||||
|
day: row.day - nptH / 24,
|
||||||
|
date: fractionalSum(row.date, -nptH, 'hour'),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = { datasets: [
|
||||||
|
makeDataset(operations?.fact, 'Факт', '#0A0', 3),
|
||||||
|
makeDataset(operations?.predict, 'Прогноз', 'purple', 1, [7, 3]),
|
||||||
|
makeDataset(operations?.plan, 'План', '#F00', 3),
|
||||||
|
makeDataset(withoutNpt, 'Факт без НПВ', '#00F', 3)
|
||||||
|
]}
|
||||||
|
|
||||||
|
if (chartRef.current && !chart) {
|
||||||
|
const thisOptions = {}
|
||||||
|
Object.assign(thisOptions, defaultOptions)
|
||||||
|
thisOptions.onClick = onPointClick
|
||||||
|
thisOptions.scales.x = scaleTypes[xLabel]
|
||||||
|
thisOptions.parsing.xAxisKey = xLabel
|
||||||
|
|
||||||
|
const newChart = new Chart(chartRef.current, {
|
||||||
|
type: 'line',
|
||||||
|
plugins: [ChartDataLabels],
|
||||||
|
options: thisOptions,
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
setChart(newChart)
|
||||||
|
|
||||||
|
return () => chart?.destroy()
|
||||||
|
} else {
|
||||||
|
chart.data = data
|
||||||
|
chart.options.scales.x = scaleTypes[xLabel]
|
||||||
|
chart.options.parsing.xAxisKey = xLabel
|
||||||
|
chart.update()
|
||||||
|
// Обнуление ширины необходимо для уменьшения размена при resize после появления элементов
|
||||||
|
chart.canvas.parentNode.style.width = '0'
|
||||||
|
chart.resize()
|
||||||
|
}
|
||||||
|
}, [chart, operations, xLabel, onPointClick])
|
||||||
|
|
||||||
|
const toogleTable = useCallback(() => {
|
||||||
|
setOperations(pre => ({ ...pre }))
|
||||||
|
setTableVisible(v => !v)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'container tvd-page'}>
|
||||||
|
<div className={'tvd-top'}>
|
||||||
|
<h2>{title || 'График Глубина-день'}</h2>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
checkedChildren={'Дата'}
|
||||||
|
unCheckedChildren={'Дни со старта'}
|
||||||
|
loading={isLoading}
|
||||||
|
onChange={(checked) => setXLabel(checked ? 'date' : 'day')}
|
||||||
|
style={{ marginRight: '20px' }}
|
||||||
|
/>
|
||||||
|
<NetGraphExport idWell={idWell} />
|
||||||
|
<Button icon={tableVisible ? <DoubleRightOutlined /> : <DoubleLeftOutlined />} onClick={toogleTable}>НПВ</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<LoaderPortal show={isLoading} style={{ flex: 1 }}>
|
||||||
|
<div className={'tvd-main'}>
|
||||||
|
<div className={'tvd-left'}>
|
||||||
|
<AdditionalTables operations={operations} xLabel={xLabel} setIsLoading={setIsLoading} />
|
||||||
|
<canvas ref={chartRef} />
|
||||||
|
</div>
|
||||||
|
{tableVisible && <NptTable operations={operations?.fact} />}
|
||||||
|
</div>
|
||||||
|
</LoaderPortal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Tvd
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback, memo } from 'react'
|
import { useState, useEffect, useCallback, memo, useMemo } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
@ -54,15 +54,15 @@ export const WellDrillParams = memo(({ idWell }) => {
|
|||||||
await updateParams()
|
await updateParams()
|
||||||
})(), [idWell, updateParams])
|
})(), [idWell, updateParams])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: DrillParamsService,
|
service: DrillParamsService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateParams,
|
onComplete: updateParams,
|
||||||
idWell
|
idWell
|
||||||
}
|
}), [idWell, updateParams])
|
||||||
|
|
||||||
const recordParser = (record) => ({ ...record, idWell })
|
const recordParser = useCallback((record) => ({ ...record, idWell }), [idWell])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<LoaderPortal show={showLoader}>
|
||||||
|
@ -1,86 +1,88 @@
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { Input } from 'antd'
|
import { Input } from 'antd'
|
||||||
import { useState, useEffect, memo } from 'react'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { useState, useEffect, memo, useMemo, useCallback } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
DatePickerWrapper,
|
|
||||||
makeColumn,
|
makeColumn,
|
||||||
makeDateSorter,
|
|
||||||
makeNumericColumnOptions,
|
makeNumericColumnOptions,
|
||||||
makeSelectColumn,
|
makeSelectColumn,
|
||||||
makeActionHandler,
|
makeActionHandler,
|
||||||
|
makeDateColumn,
|
||||||
|
makeNumericColumn,
|
||||||
|
makeNumericRender,
|
||||||
|
makeNumericSorter,
|
||||||
|
makeTextColumn,
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import { WellOperationService} from '@api'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { hasPermission } from '@utils/permissions'
|
import { hasPermission } from '@utils/permissions'
|
||||||
import { formatDate } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
|
import { WellOperationService } from '@api'
|
||||||
|
|
||||||
const { TextArea } = Input
|
const { TextArea } = Input
|
||||||
|
|
||||||
const basePageSize = 160
|
const basePageSize = 160
|
||||||
|
const dayRender = makeNumericRender(2)
|
||||||
|
const dayWithoutNptRender = (_, row) => dayRender((row.day ?? 0) - (row.nptHours ?? 0) / 24)
|
||||||
|
|
||||||
const defaultColumns = [
|
const generateColumns = (showNpt = false, categories = [], sectionTypes = []) => [
|
||||||
makeSelectColumn('Конструкция секции', 'idWellSectionType', [], undefined, {
|
makeSelectColumn('Конструкция секции', 'idWellSectionType', sectionTypes, undefined, {
|
||||||
|
sorter: makeNumericSorter('idWellSectionType'),
|
||||||
editable: true,
|
editable: true,
|
||||||
width: 160,
|
width: 160,
|
||||||
formItemRules: [({ getFieldValue }) => ({
|
|
||||||
validator(_, value) {
|
|
||||||
if (value?.length > 0)
|
|
||||||
return Promise.resolve()
|
|
||||||
return Promise.reject('Это обязательное поле!')
|
|
||||||
}
|
|
||||||
})],
|
|
||||||
}),
|
}),
|
||||||
makeSelectColumn('Операция', 'idCategory', [], undefined, { editable: true, width: 200 }),
|
makeSelectColumn('Операция', 'idCategory', categories, undefined, {
|
||||||
makeColumn('Доп. инфо', 'categoryInfo', { editable: true, width: 300, input: <TextArea/> }),
|
sorter: makeNumericSorter('idCategory'),
|
||||||
makeColumn('Глубина забоя на начало, м', 'depthStart', makeNumericColumnOptions(2, 'depthStart')),
|
|
||||||
makeColumn('Глубина забоя при завершении, м', 'depthEnd', makeNumericColumnOptions(2, 'depthEnd')),
|
|
||||||
makeColumn('Время начала', 'dateStart', {
|
|
||||||
editable: true,
|
editable: true,
|
||||||
width: 200,
|
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')),
|
makeTextColumn('Доп. инфо', 'categoryInfo', null, null, null, { editable: true, width: 300, input: <TextArea/> }),
|
||||||
makeColumn('Комментарий', 'comment', { editable: true, input: <TextArea/> }),
|
makeColumn('Глубина забоя на начало, м', 'depthStart', makeNumericColumnOptions(2, 'depthStart')),
|
||||||
]
|
makeColumn('Глубина забоя при завершении, м', 'depthEnd', makeNumericColumnOptions(2, 'depthEnd')),
|
||||||
|
makeDateColumn('Время начала', 'dateStart', undefined, undefined, {
|
||||||
|
editable: true,
|
||||||
|
width: 170,
|
||||||
|
initialValue: moment().format(),
|
||||||
|
}),
|
||||||
|
makeNumericColumn('День', 'day', null, null, dayRender, 80),
|
||||||
|
showNpt && makeNumericColumn('День без НПВ', 'dayWithoutNpt', null, null, dayWithoutNptRender, 80),
|
||||||
|
makeColumn('Часы', 'durationHours', { ...makeNumericColumnOptions(2, 'durationHours'), width: 70 }),
|
||||||
|
makeTextColumn('Комментарий', 'comment', null, null, null, { editable: true, input: <TextArea/> }),
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
export const WellOperationsEditor = memo(({ idWell, idType, ...other }) => {
|
export const WellOperationsEditor = memo(({ idWell, idType, showNpt, ...other }) => {
|
||||||
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 [operations, setOperations] = useState([])
|
const [operations, setOperations] = useState([])
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [columns, setColumns] = useState(defaultColumns)
|
|
||||||
|
const [categories, setCategories] = useState([])
|
||||||
|
const [sectionTypes, setSectionTypes] = useState([])
|
||||||
|
|
||||||
|
const columns = useMemo(() => generateColumns(showNpt, categories, sectionTypes), [showNpt, categories, sectionTypes])
|
||||||
|
|
||||||
|
const location = useLocation()
|
||||||
|
const selectedIds = useMemo(() => {
|
||||||
|
const query = new URLSearchParams(location.search)
|
||||||
|
return arrayOrDefault(query.get('selectedId')?.split(',')?.map(parseInt))
|
||||||
|
}, [location])
|
||||||
|
|
||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
let categories = await WellOperationService.getCategories(idWell) ?? []
|
const categories = arrayOrDefault(await WellOperationService.getCategories(idWell))
|
||||||
categories = categories.map((item) => ({ value: item.id, label: item.name }))
|
setCategories(categories.map((item) => ({ value: item.id, label: item.name })))
|
||||||
|
|
||||||
let sectionTypes = await WellOperationService.getSectionTypes(idWell) ?? []
|
const sectionTypes = Object.entries(await WellOperationService.getSectionTypes(idWell) ?? {})
|
||||||
sectionTypes = Object.keys(sectionTypes).map((key) => ({ value: parseInt(key), label: sectionTypes[key] }))
|
setSectionTypes(sectionTypes.map(([id, label]) => ({ value: parseInt(id), label })))
|
||||||
|
|
||||||
setColumns(preColumns => {
|
|
||||||
const newColumns = [...preColumns]
|
|
||||||
newColumns[0] = makeSelectColumn('Конструкция секции', 'idWellSectionType', sectionTypes, undefined, { editable: true, width: 160 })
|
|
||||||
newColumns[1] = makeSelectColumn('Операция', 'idCategory', categories, undefined, { editable: true, width: 200 })
|
|
||||||
return newColumns
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
'Не удалось загрузить список операций по скважине',
|
'Не удалось загрузить список операций по скважине',
|
||||||
'Получение списка операций по скважине'
|
'Получение списка операций по скважине'
|
||||||
), [idWell])
|
), [idWell])
|
||||||
|
|
||||||
const updateOperations = () => invokeWebApiWrapperAsync(
|
const updateOperations = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const skip = ((pageNumAndPageSize.current - 1) * pageNumAndPageSize.pageSize) || 0
|
const skip = ((pageNumAndPageSize.current - 1) * pageNumAndPageSize.pageSize) || 0
|
||||||
const take = pageNumAndPageSize.pageSize
|
const take = pageNumAndPageSize.pageSize
|
||||||
@ -95,17 +97,22 @@ export const WellOperationsEditor = memo(({ idWell, idType, ...other }) => {
|
|||||||
setShowLoader,
|
setShowLoader,
|
||||||
'Не удалось загрузить список операций по скважине',
|
'Не удалось загрузить список операций по скважине',
|
||||||
'Получение списка операций по скважине'
|
'Получение списка операций по скважине'
|
||||||
)
|
), [idWell, idType, pageNumAndPageSize])
|
||||||
|
|
||||||
useEffect(updateOperations, [idWell, idType, pageNumAndPageSize])
|
useEffect(updateOperations, [updateOperations])
|
||||||
|
|
||||||
const handlerProps = {
|
const handlerProps = useMemo(() => ({
|
||||||
service: WellOperationService,
|
service: WellOperationService,
|
||||||
setLoader: setShowLoader,
|
setLoader: setShowLoader,
|
||||||
errorMsg: `Не удалось выполнить операцию`,
|
errorMsg: `Не удалось выполнить операцию`,
|
||||||
onComplete: updateOperations,
|
onComplete: updateOperations,
|
||||||
idWell
|
idWell
|
||||||
}
|
}), [idWell, updateOperations])
|
||||||
|
|
||||||
|
const onRow = useCallback((record) => {
|
||||||
|
if (selectedIds?.includes(record.id))
|
||||||
|
return { style: { background: '#BF0000A0' } }
|
||||||
|
}, [selectedIds])
|
||||||
|
|
||||||
const recordParser = (record) => ({
|
const recordParser = (record) => ({
|
||||||
...record,
|
...record,
|
||||||
@ -133,6 +140,7 @@ export const WellOperationsEditor = memo(({ idWell, idType, ...other }) => {
|
|||||||
onChange: (page, pageSize) => setPageNumAndPageSize({ current: page, pageSize })
|
onChange: (page, pageSize) => setPageNumAndPageSize({ current: page, pageSize })
|
||||||
}}
|
}}
|
||||||
tableName={'well_operationse_editor'}
|
tableName={'well_operationse_editor'}
|
||||||
|
onRow={onRow}
|
||||||
/>
|
/>
|
||||||
</LoaderPortal>
|
</LoaderPortal>
|
||||||
)
|
)
|
||||||
|
@ -59,7 +59,7 @@ export const WellOperations = memo(({ idWell }) => {
|
|||||||
<WellOperationsEditor idWell={idWell} idType={0} tableName={'well_operations_plan'}/>
|
<WellOperationsEditor idWell={idWell} idType={0} tableName={'well_operations_plan'}/>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
<PrivateRoute path={`${rootPath}/fact`}>
|
<PrivateRoute path={`${rootPath}/fact`}>
|
||||||
<WellOperationsEditor idWell={idWell} idType={1} tableName={'well_operations_fact'}/>
|
<WellOperationsEditor idWell={idWell} idType={1} showNpt tableName={'well_operations_fact'}/>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
<PrivateRoute path={`${rootPath}/drillProcessFlow`}>
|
<PrivateRoute path={`${rootPath}/drillProcessFlow`}>
|
||||||
<DrillProcessFlow idWell={idWell} />
|
<DrillProcessFlow idWell={idWell} />
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
.telemetry_select {
|
|
||||||
min-width: 300px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.telemetry_select .telemetry_used {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.telemetry_select .telemetry_unused {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
8
src/styles/charts.less
Normal file
8
src/styles/charts.less
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.d3-chart {
|
||||||
|
> .d3-chart-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
background: #AAAAAAC0;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
11
src/styles/components/telemetry_select.less
Normal file
11
src/styles/components/telemetry_select.less
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.telemetry_select {
|
||||||
|
min-width: 300px !important;
|
||||||
|
|
||||||
|
& .telemetry_used {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .telemetry_unused {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
}
|
65
src/styles/tvd.less
Normal file
65
src/styles/tvd.less
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
.tvd-page {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.tvd-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tvd-main {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
|
||||||
|
.tvd-left {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
position: absolute;
|
||||||
|
//pointer-events: none;
|
||||||
|
transition: all .25s ease-out;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tvd-tr-table {
|
||||||
|
right: 15px;
|
||||||
|
top: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tvd-bl-table {
|
||||||
|
bottom: 35px;
|
||||||
|
left: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tvd-right {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding-left: 10px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.tvd-npt-filter {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ import { SimpleTimezoneDto } from '@api'
|
|||||||
|
|
||||||
export type RawDate = number | string | Date
|
export type RawDate = number | string | Date
|
||||||
|
|
||||||
export const defaultFormat: string = 'YYYY.MM.DD HH:mm'
|
export const defaultFormat: string = 'DD.MM.YYYY HH:mm'
|
||||||
|
|
||||||
export enum timeInS {
|
export enum timeInS {
|
||||||
millisecond = 0.001,
|
millisecond = 0.001,
|
||||||
@ -45,8 +45,8 @@ export const calcDuration = (start: unknown, end: unknown) => {
|
|||||||
return (+new Date(end) - +new Date(start)) * timeInS.millisecond / timeInS.day
|
return (+new Date(end) - +new Date(start)) * timeInS.millisecond / timeInS.day
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate => {
|
export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate | null => {
|
||||||
if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return NaN
|
if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return null
|
||||||
const d = new Date(date)
|
const d = new Date(date)
|
||||||
d.setMilliseconds(d.getMilliseconds() + value * timeInS[type] * 1000)
|
d.setMilliseconds(d.getMilliseconds() + value * timeInS[type] * 1000)
|
||||||
return d
|
return d
|
||||||
|
@ -7,28 +7,37 @@ export const getPrecision = (number: number) => Number.isFinite(number) ? number
|
|||||||
|
|
||||||
export type KeyType = number | string
|
export type KeyType = number | string
|
||||||
|
|
||||||
export type SaubData = {
|
export const nwtIdCategory = 1043
|
||||||
key?: number
|
|
||||||
depth?: number
|
export type SaubData = WellOperationDto & {
|
||||||
date?: string
|
isNPT?: boolean // Относится ли операция к НПВ
|
||||||
day?: number
|
key?: number // ID операции
|
||||||
|
depth?: number // Глубина
|
||||||
|
date?: string // Дата
|
||||||
|
nptHours?: number // Колличество часов НПВ с начала бурения до текущего момента
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getOperations = async (idWell: number): Promise<{
|
export const getOperations = async (idWell: number): Promise<{
|
||||||
operations?: WellOperationDtoPlanFactPredictBase[],
|
operations: WellOperationDtoPlanFactPredictBase[],
|
||||||
plan?: SaubData[]
|
plan: SaubData[]
|
||||||
fact?: SaubData[]
|
fact: SaubData[]
|
||||||
predict?: SaubData[]
|
predict: SaubData[]
|
||||||
}> => {
|
}> => {
|
||||||
const ops = await OperationStatService.getTvd(idWell)
|
const ops = await OperationStatService.getTvd(idWell)
|
||||||
|
|
||||||
if (!ops) return {}
|
if (!ops) return {
|
||||||
|
operations: [],
|
||||||
|
plan: [],
|
||||||
|
fact: [],
|
||||||
|
predict: [],
|
||||||
|
}
|
||||||
|
|
||||||
const convert = (operation?: WellOperationDto): SaubData => ({
|
const convert = (operation?: WellOperationDto): SaubData => ({
|
||||||
|
...operation,
|
||||||
key: operation?.id,
|
key: operation?.id,
|
||||||
depth: operation?.depthStart,
|
depth: operation?.depthStart,
|
||||||
date: operation?.dateStart,
|
date: operation?.dateStart,
|
||||||
day: operation?.day,
|
isNPT: operation?.idCategory === nwtIdCategory,
|
||||||
})
|
})
|
||||||
|
|
||||||
const planData = ops
|
const planData = ops
|
||||||
|
@ -78,7 +78,10 @@ export const requirements: PermissionRecord = {
|
|||||||
permission: ['AdminPermission.get'],
|
permission: ['AdminPermission.get'],
|
||||||
role: ['AdminUserRole.get', 'AdminPermission.get'],
|
role: ['AdminUserRole.get', 'AdminPermission.get'],
|
||||||
visit_log: ['RequerstTracker.get'],
|
visit_log: ['RequerstTracker.get'],
|
||||||
telemetry: ['AdminTelemetry.get'],
|
telemetry: {
|
||||||
|
merger: ['AdminTelemetry.get'],
|
||||||
|
viewer: ['AdminTelemetry.get'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
deposit: ['Deposit.get', 'Cluster.get'],
|
deposit: ['Deposit.get', 'Cluster.get'],
|
||||||
cluster: {
|
cluster: {
|
||||||
|
Loading…
Reference in New Issue
Block a user