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 PageHeader from '@components/PageHeader'
|
||||
import WellTreeSelector from '@components/WellTreeSelector'
|
||||
import WellTreeSelector from '@components/selectors/WellTreeSelector'
|
||||
|
||||
export type LayoutPortalProps = LayoutProps & {
|
||||
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 { ColumnProps } from 'antd/lib/table'
|
||||
|
||||
export { makeDateColumn } from './date'
|
||||
export {
|
||||
RegExpIsFloat,
|
||||
makeNumericRender,
|
||||
|
@ -14,8 +14,8 @@ export const makeSelectColumn = <T extends unknown = string>(
|
||||
...other,
|
||||
input: <Select options={options} {...selectOther}/>,
|
||||
render: (value) => {
|
||||
const item = options?.find(option => option?.value === value)
|
||||
return other?.render?.(item?.value) ?? item?.label ?? defaultValue ?? value ?? '--'
|
||||
const item = options?.find(option => option?.value === value)
|
||||
return other?.render?.(item?.value) ?? item?.label ?? defaultValue ?? value ?? '--'
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -6,8 +6,8 @@ import moment, { Moment } from 'moment'
|
||||
import { defaultFormat } from '@utils'
|
||||
|
||||
export type DatePickerWrapperProps = PickerDateProps<Moment> & {
|
||||
value: Moment,
|
||||
onChange: (date: Moment | null) => any
|
||||
value?: Moment,
|
||||
onChange?: (date: Moment | null) => any
|
||||
isUTC?: boolean
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ export const DatePickerWrapper = memo<DatePickerWrapperProps>(({ value, onChange
|
||||
allowClear={false}
|
||||
format={defaultFormat}
|
||||
defaultValue={moment()}
|
||||
onChange={(date) => onChange(date)}
|
||||
onChange={(date) => onChange?.(date)}
|
||||
value={isUTC ? moment.utc(value).local() : moment(value)}
|
||||
{...other}
|
||||
/>
|
||||
|
@ -188,6 +188,7 @@ export const EditableTable = memo(({
|
||||
return {
|
||||
...col,
|
||||
onCell: (record) => ({
|
||||
...col.onCell?.(record),
|
||||
editing: isEditing(record),
|
||||
record,
|
||||
dataIndex: col.dataIndex ?? col.key,
|
||||
|
@ -6,6 +6,7 @@ export {
|
||||
RegExpIsFloat,
|
||||
timezoneOptions,
|
||||
TimezoneSelect,
|
||||
makeDateColumn,
|
||||
makeGroupColumn,
|
||||
makeColumn,
|
||||
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 { isRawDate } from '@utils'
|
||||
import LoaderPortal from './LoaderPortal'
|
||||
import { WellIcon, WellIconState } from './icons'
|
||||
import { invokeWebApiWrapperAsync } from './factory'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { WellIcon, WellIconState } from '@components/icons'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DepositService, DepositDto } from '@api'
|
||||
|
||||
import { ReactComponent as DepositIcon } from '@images/DepositIcon.svg'
|
@ -4,7 +4,7 @@ import { Tooltip } from 'antd'
|
||||
import { TelemetryDto, TelemetryInfoDto } from '@api'
|
||||
import { Grid, GridItem } from '@components/Grid'
|
||||
|
||||
const lables: Record<string, string> = {
|
||||
export const lables: Record<string, string> = {
|
||||
timeZoneId: 'Временная зона',
|
||||
timeZoneOffsetTotalHours: 'Сдвиг временной зоны',
|
||||
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 {
|
||||
EditableTable,
|
||||
@ -9,7 +10,6 @@ import {
|
||||
defaultPagination,
|
||||
makeTimezoneColumn
|
||||
} from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminClusterService, AdminDepositService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
@ -22,8 +22,16 @@ export const ClusterController = memo(() => {
|
||||
const [deposits, setDeposits] = useState([])
|
||||
const [clusters, setClusters] = useState([])
|
||||
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, '--', {
|
||||
width: 200,
|
||||
editable: true,
|
||||
@ -38,7 +46,7 @@ export const ClusterController = memo(() => {
|
||||
makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFixed }),
|
||||
makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }),
|
||||
]
|
||||
], [deposits])
|
||||
|
||||
const updateTable = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -63,27 +71,35 @@ export const ClusterController = memo(() => {
|
||||
|
||||
useEffect(updateTable, [updateTable])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminClusterService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateTable,
|
||||
}
|
||||
}), [updateTable])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска (по полям: Название, Долгота, Широта)...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
dataSource={clusters}
|
||||
size={'small'}
|
||||
loading={showLoader}
|
||||
columns={clusterColumns}
|
||||
dataSource={filteredClusters}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={hasPermission('AdminCluster.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление куста')}
|
||||
onRowEdit={hasPermission('AdminCluster.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование куста')}
|
||||
onRowDelete={hasPermission('AdminCluster.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление куста')}
|
||||
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 {
|
||||
EditableTable,
|
||||
@ -8,7 +9,6 @@ import {
|
||||
makeSelectColumn,
|
||||
defaultPagination
|
||||
} from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminCompanyService, AdminCompanyTypeService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
@ -20,6 +20,11 @@ export const CompanyController = memo(() => {
|
||||
const [columns, setColumns] = useState([])
|
||||
const [companies, setCompanies] = useState([])
|
||||
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 companies = await AdminCompanyService.getAll()
|
||||
@ -53,7 +58,7 @@ export const CompanyController = memo(() => {
|
||||
'Получение списка типов команд'
|
||||
), [updateTable])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminCompanyService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
@ -63,22 +68,30 @@ export const CompanyController = memo(() => {
|
||||
`Не удалось обновить список компаний`,
|
||||
'Получение списка компаний'
|
||||
),
|
||||
}
|
||||
}), [updateTable])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска (по полю Название)...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={companies}
|
||||
loading={showLoader}
|
||||
dataSource={filteredCompanies}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={hasPermission('AdminCompany.edit') && makeActionHandler('insert', handlerProps, null, 'Добавлениее компаний')}
|
||||
onRowEdit={hasPermission('AdminCompany.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование команий')}
|
||||
onRowDelete={hasPermission('AdminCompany.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление компаний')}
|
||||
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 {
|
||||
EditableTable,
|
||||
@ -7,7 +8,6 @@ import {
|
||||
makeStringSorter,
|
||||
defaultPagination
|
||||
} from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminCompanyTypeService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
@ -26,6 +26,11 @@ const columns = [
|
||||
export const CompanyTypeController = memo(() => {
|
||||
const [companyTypes, setCompanyTypes] = useState([])
|
||||
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(
|
||||
async() => {
|
||||
@ -39,27 +44,35 @@ export const CompanyTypeController = memo(() => {
|
||||
|
||||
useEffect(updateTable, [updateTable])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminCompanyTypeService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateTable,
|
||||
}
|
||||
}), [updateTable])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска (по полю Название)...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<EditableTable
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={companyTypes}
|
||||
loading={showLoader}
|
||||
pagination={defaultPagination}
|
||||
dataSource={filteredCompanyTypes}
|
||||
onRowAdd={hasPermission('AdminCompanyType.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление типа компаний')}
|
||||
onRowEdit={hasPermission('AdminCompanyType.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование типа компаний')}
|
||||
onRowDelete={hasPermission('AdminCompanyType.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление типа компаний')}
|
||||
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 { 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 { min1 } from '@utils/validationRules'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { AdminDepositService } from '@api'
|
||||
|
||||
export const coordsFixed = (coords) => coords && isFinite(coords) ? (+coords).toPrecision(10) : '-'
|
||||
|
||||
@ -20,6 +20,14 @@ const depositColumns = [
|
||||
export const DepositController = memo(() => {
|
||||
const [deposits, setDeposits] = useState([])
|
||||
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(
|
||||
async() => {
|
||||
@ -33,27 +41,35 @@ export const DepositController = memo(() => {
|
||||
|
||||
useEffect(updateTable, [updateTable])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminDepositService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateTable,
|
||||
}
|
||||
}), [updateTable])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска (по полям: Название, Долгота, Широта)...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
dataSource={deposits}
|
||||
size={'small'}
|
||||
loading={showLoader}
|
||||
columns={depositColumns}
|
||||
dataSource={filteredDeposits}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={hasPermission('AdminDeposit.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление месторождения')}
|
||||
onRowEdit={hasPermission('AdminDeposit.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование месторождения')}
|
||||
onRowDelete={hasPermission('AdminDeposit.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление месторождения')}
|
||||
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 {
|
||||
EditableTable,
|
||||
@ -6,7 +7,6 @@ import {
|
||||
makeColumn,
|
||||
makeStringSorter
|
||||
} from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { AdminPermissionService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
@ -27,8 +27,15 @@ const columns = [
|
||||
]
|
||||
|
||||
export const PermissionController = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
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(
|
||||
async () => {
|
||||
@ -42,27 +49,35 @@ export const PermissionController = memo(() => {
|
||||
|
||||
useEffect(() => updateTable(), [updateTable])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminPermissionService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateTable
|
||||
}
|
||||
}), [updateTable])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска (по полям: Название, Описание)...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<EditableTable
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={permissions}
|
||||
loading={showLoader}
|
||||
dataSource={filteredPermissions}
|
||||
pagination={{ showSizeChanger: true }}
|
||||
onRowAdd={hasPermission('AdminPermission.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление права')}
|
||||
onRowEdit={hasPermission('AdminPermission.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование права')}
|
||||
onRowDelete={hasPermission('AdminPermission.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление права')}
|
||||
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 { 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 { arrayOrDefault } from '@utils'
|
||||
import { min1 } from '@utils/validationRules'
|
||||
@ -13,29 +13,31 @@ export const RoleController = memo(() => {
|
||||
const [permissions, setPermissions] = useState([])
|
||||
const [roles, setRoles] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [columns, setColumns] = useState([])
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
|
||||
const filteredRoles = useMemo(() => roles.filter((role) => role && (!searchValue ||
|
||||
role.caption?.toLowerCase()?.includes(searchValue.toLowerCase())
|
||||
)), [roles, searchValue])
|
||||
|
||||
const columns = useMemo(() => [
|
||||
makeTextColumn('Название', 'caption', null, null, null, { width: 100, editable: true, formItemRules: min1 }),
|
||||
makeTagColumn('Включённые роли', 'roles', roles, 'id', 'caption', {
|
||||
width: 400,
|
||||
editable: true,
|
||||
render: (role) => <RoleView role={role} />
|
||||
}, { allowClear: true }),
|
||||
makeTagColumn('Разрешения', 'permissions', permissions, 'id', 'name', {
|
||||
width: 600,
|
||||
editable: true,
|
||||
render: (permission) => <PermissionView info={permission} />,
|
||||
}),
|
||||
], [roles, permissions])
|
||||
|
||||
const loadRoles = useCallback(async () => {
|
||||
const roles = await AdminUserRoleService.getAll()
|
||||
setRoles(arrayOrDefault(roles))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setColumns([
|
||||
makeColumn('Название', 'caption', { width: 100, editable: true, formItemRules: min1 }),
|
||||
makeTagColumn('Включённые роли', 'roles', roles, 'id', 'caption', {
|
||||
width: 400,
|
||||
editable: true,
|
||||
render: (role) => <RoleView role={role} />
|
||||
}, { allowClear: true }),
|
||||
makeTagColumn('Разрешения', 'permissions', permissions, 'id', 'name', {
|
||||
width: 600,
|
||||
editable: true,
|
||||
render: (permission) => <PermissionView info={permission} />,
|
||||
}),
|
||||
])
|
||||
}, [roles, permissions])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const permissions = await AdminPermissionService.getAll()
|
||||
@ -47,7 +49,7 @@ export const RoleController = memo(() => {
|
||||
'Получение списка ролей'
|
||||
), [loadRoles])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminUserRoleService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
@ -55,23 +57,31 @@ export const RoleController = memo(() => {
|
||||
loadRoles,
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список ролей`,
|
||||
'Получение списка ролей'
|
||||
'Получение списка ролей',
|
||||
)
|
||||
}
|
||||
}), [loadRoles])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска (по полю Название)...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<EditableTable
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={roles}
|
||||
loading={showLoader}
|
||||
dataSource={filteredRoles}
|
||||
onRowAdd={hasPermission('AdminUserRole.edit') && makeActionHandler('insert', handlerProps, null, 'Добавление роли')}
|
||||
onRowEdit={hasPermission('AdminUserRole.edit') && makeActionHandler('update', handlerProps, null, 'Редактирование роли')}
|
||||
onRowDelete={hasPermission('AdminUserRole.delete') && makeActionHandler('delete', handlerProps, null, 'Удаление роли')}
|
||||
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 { memo, useEffect, useState } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
export const RoleTag = memo(({ roles, value, onChange }) => {
|
||||
const [options, setOptions] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(roles.map((elm) => ({
|
||||
key: Date.now(),
|
||||
value: `${elm.caption}`,
|
||||
label: elm.caption
|
||||
})))
|
||||
}, [roles])
|
||||
const options = useMemo(() => roles.map((elm) => ({
|
||||
key: Date.now(),
|
||||
value: `${elm.caption}`,
|
||||
label: elm.caption
|
||||
})), [roles])
|
||||
|
||||
return (
|
||||
<Select
|
||||
|
@ -1,16 +1,15 @@
|
||||
import { Button, Input, Tag } from 'antd'
|
||||
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 {
|
||||
EditableTable,
|
||||
makeColumn,
|
||||
makeSelectColumn,
|
||||
makeActionHandler,
|
||||
makeStringSorter,
|
||||
makeNumericSorter,
|
||||
defaultPagination
|
||||
defaultPagination,
|
||||
makeTextColumn
|
||||
} from '@components/Table'
|
||||
import { RoleView } from '@components/views'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
@ -38,7 +37,7 @@ export const UserController = memo(() => {
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const filteredUsers = users.filter((user) => user && [
|
||||
const filteredUsers = users.filter((user) => user && (!searchValue || [
|
||||
user.login ?? '',
|
||||
user.name ?? '',
|
||||
user.surname ?? '',
|
||||
@ -47,7 +46,7 @@ export const UserController = memo(() => {
|
||||
user.phone ?? '',
|
||||
user.position ?? '',
|
||||
user.company?.caption ?? '',
|
||||
].join(' ').toLowerCase().includes(searchValue))
|
||||
].join(' ').toLowerCase().includes(searchValue.toLowerCase())))
|
||||
setFilteredUsers(filteredUsers)
|
||||
},
|
||||
setIsSearching,
|
||||
@ -106,8 +105,14 @@ export const UserController = memo(() => {
|
||||
const filters = makeTextFilters(users, ['surname', 'name', 'patronymic', 'email'])
|
||||
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([
|
||||
makeColumn('Логин', 'login', {
|
||||
makeTextColumn('Логин', 'login', null, null, null, {
|
||||
editable: true,
|
||||
formItemRules: [
|
||||
{ required: true },
|
||||
@ -121,59 +126,40 @@ export const UserController = memo(() => {
|
||||
// })
|
||||
// TODO: Для проверки уникальности логина необходимо исключить из выборки логин выбранного пользователя
|
||||
],
|
||||
sorter: makeStringSorter('login'),
|
||||
}),
|
||||
makeColumn('Фамилия', 'surname', {
|
||||
makeTextColumn('Фамилия', 'surname', filters.surname, null, null, {
|
||||
editable: true,
|
||||
formItemRules: [{ required: true }, ...nameRules],
|
||||
sorter: makeStringSorter('surname'),
|
||||
filters: filters.surname,
|
||||
filterSearch: true,
|
||||
onFilter: makeTextOnFilter('surname'),
|
||||
}),
|
||||
makeColumn('Имя', 'name', {
|
||||
makeTextColumn('Имя', 'name', filters.name, null, null, {
|
||||
editable: true,
|
||||
formItemRules: nameRules,
|
||||
sorter: makeStringSorter('name'),
|
||||
filters: filters.name,
|
||||
filterSearch: true,
|
||||
onFilter: makeTextOnFilter('name'),
|
||||
}),
|
||||
makeColumn('Отчество', 'patronymic', {
|
||||
makeTextColumn('Отчество', 'patronymic', filters.partonymic, null, null, {
|
||||
editable: true,
|
||||
formItemRules: nameRules,
|
||||
sorter: makeStringSorter('patronymic'),
|
||||
filters: filters.patronymic,
|
||||
filterSearch: true,
|
||||
onFilter: makeTextOnFilter('patronymic'),
|
||||
}),
|
||||
makeColumn('E-mail', 'email', {
|
||||
makeTextColumn('E-mail', 'email', filters.email, null, null, {
|
||||
editable: true,
|
||||
formItemRules: [{ required: true }, ...emailRules],
|
||||
sorter: makeStringSorter('email'),
|
||||
filters: filters.email,
|
||||
filterSearch: true,
|
||||
onFilter: makeTextOnFilter('email'),
|
||||
}),
|
||||
makeColumn('Номер телефона', 'phone', {
|
||||
makeTextColumn('Номер телефона', 'phone', null, null, null, {
|
||||
editable: true,
|
||||
formItemRules: phoneRules,
|
||||
sorter: makeStringSorter('phone'),
|
||||
}),
|
||||
makeColumn('Должность', 'position', {
|
||||
editable: true,
|
||||
sorter: makeStringSorter('position'),
|
||||
}),
|
||||
makeColumn('Роли', 'roleNames', {
|
||||
makeTextColumn('Должность', 'position', null, null, null, { editable: true }),
|
||||
makeTextColumn('Роли', 'roleNames', roleFilters, null, rolesRender, {
|
||||
editable: true,
|
||||
input: <RoleTag roles={roles} />,
|
||||
filters: roleFilters,
|
||||
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, '--', {
|
||||
editable: true,
|
||||
@ -186,12 +172,12 @@ export const UserController = memo(() => {
|
||||
'Получение списка компаний'
|
||||
), [])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminUserService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateTable,
|
||||
}
|
||||
}), [updateTable])
|
||||
|
||||
const onSearchTextChange = useCallback((e) => subject?.next(e?.target?.value), [subject])
|
||||
|
||||
@ -200,7 +186,7 @@ export const UserController = memo(() => {
|
||||
<LoaderPortal show={showLoader}>
|
||||
<Input.Search
|
||||
allowClear
|
||||
placeholder={'Поиск пользователей'}
|
||||
placeholder={'Введите текст для поиска (по всем полям за исключением ролей)...'}
|
||||
onChange={onSearchTextChange}
|
||||
style={{ marginBottom: '15px' }}
|
||||
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 { defaultPagination, makeColumn, makeDateSorter, makeStringSorter, Table } from '@components/Table'
|
||||
import { RequestTrackerService } from '@api'
|
||||
@ -12,14 +12,22 @@ const columns = [
|
||||
makeColumn('Логин', 'login', { sorter: makeStringSorter('login') }),
|
||||
makeColumn('IP', 'ip', { sorter: makeStringSorter('ip') }),
|
||||
makeColumn('Дата посещения', 'lastDate', {
|
||||
render: (date) => formatDate(date, false, 'DD MMM YYYY, HH:mm:ss'),
|
||||
render: (date) => formatDate(date, false),
|
||||
sorter: makeDateSorter('lastDate'),
|
||||
}),
|
||||
]
|
||||
|
||||
export const VisitLog = memo(() => {
|
||||
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(
|
||||
async () => {
|
||||
@ -27,23 +35,30 @@ export const VisitLog = memo(() => {
|
||||
logData.forEach((log) => log.key = `${log.login}${log.ip}`)
|
||||
setLogData(logData)
|
||||
},
|
||||
setIsLoading,
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список последних посещений пользователей`,
|
||||
'Получение списка последних посещений'
|
||||
), [])
|
||||
|
||||
|
||||
return (
|
||||
<LoaderPortal show={isLoading}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<Table
|
||||
size={'small'}
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={logData}
|
||||
loading={showLoader}
|
||||
dataSource={filteredLogData}
|
||||
pagination={defaultPagination}
|
||||
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 { memo, useEffect, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import {
|
||||
AdminClusterService,
|
||||
@ -19,16 +19,13 @@ import {
|
||||
defaultPagination,
|
||||
makeTimezoneColumn,
|
||||
} from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { TelemetryView, CompanyView } from '@components/views'
|
||||
import TelemetrySelect from '@components/selectors/TelemetrySelect'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
|
||||
import { coordsFixed } from '../DepositController'
|
||||
import TelemetrySelect from './TelemetrySelect'
|
||||
|
||||
import '@styles/admin.css'
|
||||
|
||||
const wellTypes = [
|
||||
{ value: 1, label: 'Наклонно-направленная' },
|
||||
@ -44,8 +41,17 @@ export const WellController = memo(() => {
|
||||
const [columns, setColumns] = useState([])
|
||||
const [wells, setWells] = useState([])
|
||||
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 () => {
|
||||
const wells = await AdminWellService.getAll()
|
||||
setWells(arrayOrDefault(wells))
|
||||
@ -53,11 +59,11 @@ export const WellController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список скважин`,
|
||||
'Получение списка скважин'
|
||||
)
|
||||
), [])
|
||||
|
||||
const duplicateWell = (well) => {
|
||||
const duplicateWell = useCallback((well) => {
|
||||
// TODO: Метод дубликации скважины
|
||||
}
|
||||
}, [])
|
||||
|
||||
const addititonalButtons = memo((record, editingKey) => (
|
||||
<Button
|
||||
@ -112,22 +118,30 @@ export const WellController = memo(() => {
|
||||
setShowLoader,
|
||||
`Не удалось загрузить список кустов`,
|
||||
'Получение списка кустов'
|
||||
), [])
|
||||
), [updateTable])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: AdminWellService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateTable
|
||||
}
|
||||
}), [updateTable])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
<>
|
||||
<Input.Search
|
||||
style={{ margin: '15px 0' }}
|
||||
placeholder={'Введите текст для поиска (по полям: Название, Долгота, Широта)...'}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
loading={showLoader}
|
||||
/>
|
||||
<EditableTable
|
||||
size={'small'}
|
||||
bordered
|
||||
size={'small'}
|
||||
columns={columns}
|
||||
dataSource={wells}
|
||||
loading={showLoader}
|
||||
dataSource={filteredWells}
|
||||
pagination={defaultPagination}
|
||||
onRowAdd={hasPermission('AdminWell.edit') && makeActionHandler('insert', handlerProps, recordParser, 'Добавление скважины')}
|
||||
onRowEdit={hasPermission('AdminWell.edit') && makeActionHandler('update', handlerProps, recordParser, 'Редактирование скважины')}
|
||||
@ -136,7 +150,7 @@ export const WellController = memo(() => {
|
||||
buttonsWidth={95}
|
||||
tableName={'admin_well_controller'}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Layout, Menu } from 'antd'
|
||||
import { lazy, Suspense } from 'react'
|
||||
import { lazy, memo, Suspense } from 'react'
|
||||
import { Switch, useParams } from 'react-router-dom'
|
||||
|
||||
import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private'
|
||||
@ -14,12 +14,12 @@ const WellController = lazy(() => import( './WellController'))
|
||||
const RoleController = lazy(() => import( './RoleController'))
|
||||
const CompanyTypeController = lazy(() => import('./CompanyTypeController'))
|
||||
const PermissionController = lazy(() => import( './PermissionController'))
|
||||
const TelemetryController = lazy(() => import( './TelemetryController'))
|
||||
const TelemetrySection = lazy(() => import( './Telemetry'))
|
||||
const VisitLog = lazy(() => import( './VisitLog'))
|
||||
|
||||
const rootPath = '/admin'
|
||||
|
||||
export const AdminPanel = () => {
|
||||
export const AdminPanel = memo(() => {
|
||||
const { tab } = useParams()
|
||||
|
||||
return (
|
||||
@ -33,7 +33,7 @@ export const AdminPanel = () => {
|
||||
<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={'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={'Журнал посещений'} />
|
||||
</Menu>
|
||||
|
||||
@ -41,16 +41,16 @@ export const AdminPanel = () => {
|
||||
<Layout.Content className={'site-layout-background'}>
|
||||
<Suspense fallback={<SuspenseFallback />}>
|
||||
<Switch>
|
||||
<PrivateRoute path={`${rootPath}/deposit` } component={ DepositController} />
|
||||
<PrivateRoute path={`${rootPath}/cluster` } component={ ClusterController} />
|
||||
<PrivateRoute path={`${rootPath}/well` } component={ WellController} />
|
||||
<PrivateRoute path={`${rootPath}/user` } component={ UserController} />
|
||||
<PrivateRoute path={`${rootPath}/company` } component={ CompanyController} />
|
||||
<PrivateRoute path={`${rootPath}/company_type`} component={CompanyTypeController} />
|
||||
<PrivateRoute path={`${rootPath}/role` } component={ RoleController} />
|
||||
<PrivateRoute path={`${rootPath}/permission` } component={ PermissionController} />
|
||||
<PrivateRoute path={`${rootPath}/telemetry` } component={ TelemetryController} />
|
||||
<PrivateRoute path={`${rootPath}/visit_log` } component={VisitLog} />
|
||||
<PrivateRoute path={`${rootPath}/deposit` } component={ DepositController} />
|
||||
<PrivateRoute path={`${rootPath}/cluster` } component={ ClusterController} />
|
||||
<PrivateRoute path={`${rootPath}/well` } component={ WellController} />
|
||||
<PrivateRoute path={`${rootPath}/user` } component={ UserController} />
|
||||
<PrivateRoute path={`${rootPath}/company` } component={ CompanyController} />
|
||||
<PrivateRoute path={`${rootPath}/company_type` } component={CompanyTypeController} />
|
||||
<PrivateRoute path={`${rootPath}/role` } component={ RoleController} />
|
||||
<PrivateRoute path={`${rootPath}/permission` } component={ PermissionController} />
|
||||
<PrivateRoute path={`${rootPath}/telemetry/:tab?`} component={TelemetrySection} />
|
||||
<PrivateRoute path={`${rootPath}/visit_log` } component={VisitLog} />
|
||||
<PrivateDefaultRoute urls={[
|
||||
`${rootPath}/deposit`,
|
||||
`${rootPath}/cluster`,
|
||||
@ -69,6 +69,6 @@ export const AdminPanel = () => {
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default AdminPanel
|
||||
|
@ -2,8 +2,8 @@ import { Table as RawTable, Typography } from 'antd'
|
||||
import { Fragment, memo, useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { WellSelector } from '@components/WellSelector'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { WellSelector } from '@components/selectors/WellSelector'
|
||||
import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
|
||||
import { OperationStatService, WellOperationService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
@ -18,10 +18,17 @@ const { Cell, Row } = Summary
|
||||
const numericRender = makeNumericRender()
|
||||
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, [
|
||||
makeNumericColumn('Проходка', key, null, null, (section => numericRender(section?.depth)), 100),
|
||||
makeNumericColumn('Время', key, null, null, (section => numericRender(section?.time)), 100),
|
||||
makeNumericColumn((<>V<sub>рейсовая</sub></>), key, null, null, speedRender ?? speedNumericRender, 100),
|
||||
makeNumericColumn('Проходка', key, null, null, (section => numericRender(section?.depth)), 100, {
|
||||
sorter: makeSectionSorter(key, 'depth'),
|
||||
}),
|
||||
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 = [
|
||||
@ -90,21 +97,30 @@ export const Statistics = memo(({ idWell }) => {
|
||||
async () => {
|
||||
const types = await WellOperationService.getSectionTypes(idWell)
|
||||
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([
|
||||
...defaultColumns,
|
||||
...Object.entries(types).map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
||||
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`)),
|
||||
])
|
||||
setCmpColumns([
|
||||
...defaultColumns,
|
||||
...Object.entries(types).map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
||||
...filteredSections.map(([id, name]) => makeSectionColumn(name, `section_${id}`, {
|
||||
speedRender: cmpSpeedRender(`section_${id}`)
|
||||
}))
|
||||
])
|
||||
},
|
||||
setIsPageLoading,
|
||||
`Не удалось получить типы секции`,
|
||||
`Получение списка возможных секций`,
|
||||
), [idWell, cmpSpeedRender])
|
||||
'Не удалось установить необходимые столбцы'
|
||||
), [sectionTypes, avgData, cmpSpeedRender])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { 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
|
||||
|
||||
export const WellCompositeSections = memo(({ idWell, statsWells, selectedSections }) => {
|
||||
const [rows, setRows] = useState([])
|
||||
const [params, setParams] = useState([])
|
||||
const [paramsColumns, setParamsColumns] = useState([])
|
||||
const [selectedWells, setSelectedWells] = useState([])
|
||||
const [wellOperations, setWellOperations] = useState([])
|
||||
const [selectedWellsKeys, setSelectedWellsKeys] = useState([])
|
||||
@ -44,21 +42,9 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => (async() => setParamsColumns(await getColumns(idWell)))(), [idWell])
|
||||
const paramsColumns = useMemo(async() => await getColumns(idWell), [idWell])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpsModalVisible || selectedWellId <= 0) return
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const { operations } = await getOperations(selectedWellId)
|
||||
setWellOperations(operations)
|
||||
},
|
||||
setShowLoader,
|
||||
`Не удалось загрузить операции по скважине "${selectedWellId}"`,
|
||||
)
|
||||
}, [selectedWellId, isOpsModalVisible])
|
||||
|
||||
useEffect(() => {
|
||||
const rows = useMemo(() => {
|
||||
const rows = []
|
||||
statsWells?.forEach((well) => {
|
||||
well.sections?.forEach((section) => {
|
||||
@ -113,9 +99,21 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
'nonProductiveTimeFact',
|
||||
])
|
||||
|
||||
setRows(rows)
|
||||
return rows
|
||||
}, [statsWells])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpsModalVisible || selectedWellId <= 0) return
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const { operations } = await getOperations(selectedWellId)
|
||||
setWellOperations(operations)
|
||||
},
|
||||
setShowLoader,
|
||||
`Не удалось загрузить операции по скважине "${selectedWellId}"`,
|
||||
)
|
||||
}, [selectedWellId, isOpsModalVisible])
|
||||
|
||||
useEffect(() => {
|
||||
const selected = rows.filter((row) => selectedSections.some(section => (
|
||||
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))
|
||||
}, [rows, selectedSections])
|
||||
|
||||
const columns = [
|
||||
const columns = useMemo(() => [
|
||||
makeTextColumn('скв №', 'caption', null, null,
|
||||
(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>
|
||||
)) ?? '-',
|
||||
},
|
||||
]
|
||||
], [location.pathname])
|
||||
|
||||
const rowSelection = hasPermission('WellOperation.edit') && {
|
||||
const rowSelection = useMemo(() => hasPermission('WellOperation.edit') && {
|
||||
selectedRowKeys: selectedWellsKeys,
|
||||
onChange: (keys, items) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -187,7 +185,7 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
`Не удалось сохранить изменения выбранных секций для композитной скважины "${idWell}"`,
|
||||
'Изменение выбранных секций скважины'
|
||||
)
|
||||
}
|
||||
}, [idWell, selectedWellsKeys])
|
||||
|
||||
const onParamButtonClick = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import WellSelector from '@components/WellSelector'
|
||||
import WellSelector from '@components/selectors/WellSelector'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
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 { Switch, useParams } from 'react-router-dom'
|
||||
|
||||
@ -9,7 +9,7 @@ import Statistics from './Statistics'
|
||||
|
||||
export const Analytics = memo(({ idWell }) => {
|
||||
const { tab } = useParams()
|
||||
const rootPath = `/well/${idWell}/analytics`
|
||||
const rootPath = useMemo(() => `/well/${idWell}/analytics`, [idWell])
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
@ -1,21 +1,14 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { Grid, GridItem } from '@components/Grid'
|
||||
import { Column } from '@components/charts/Column'
|
||||
|
||||
export const ArchiveColumn = ({ lineGroup, data, interval, style, headerHeight, yStart }) => {
|
||||
const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
|
||||
const [pv, setPV] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const lgws = lineGroup.filter(cfg => !cfg.isShape)
|
||||
setLineGroupWithoutShapes(lgws)
|
||||
setPV(lgws.filter(line => line.showLabels).map(line => ({
|
||||
color: line.color,
|
||||
label: line.label
|
||||
})))
|
||||
}, [lineGroup])
|
||||
|
||||
export const ArchiveColumn = memo(({ lineGroup, data, interval, style, headerHeight, yStart }) => {
|
||||
const lgws = useMemo(() => lineGroup.filter(cfg => !cfg.isShape), [lineGroup])
|
||||
const pv = useMemo(() => lgws.filter(line => line.showLabels).map(line => ({
|
||||
color: line.color,
|
||||
label: line.label
|
||||
})), [lgws])
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
@ -26,13 +19,13 @@ export const ArchiveColumn = ({ lineGroup, data, interval, style, headerHeight,
|
||||
</Grid>
|
||||
<Column
|
||||
data={data}
|
||||
lineGroup={lineGroupWithoutShapes}
|
||||
lineGroup={lgws}
|
||||
interval={interval}
|
||||
yDisplay={false}
|
||||
yStart={yStart}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default ArchiveColumn
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { Grid, GridItem } from '@components/Grid'
|
||||
|
||||
@ -41,13 +41,9 @@ export const cutData = (data, beginDate, endDate) => {
|
||||
return data
|
||||
}
|
||||
|
||||
export const ArchiveDisplay = ({data, startDate, interval, onWheel}) => {
|
||||
const [chartData, setChartData] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const endDate = new Date(+startDate + interval)
|
||||
setChartData(cutData(data, startDate, endDate))
|
||||
}, [data, startDate, interval])
|
||||
export const ArchiveDisplay = memo(({data, startDate, interval, onWheel}) => {
|
||||
const endDate = useMemo(() => new Date(+startDate + interval), [startDate, interval])
|
||||
const chartData = useMemo(() => cutData(data, startDate, endDate), [data, startDate, endDate])
|
||||
|
||||
return (
|
||||
<Grid onWheel={onWheel}>
|
||||
@ -65,6 +61,6 @@ export const ArchiveDisplay = ({data, startDate, interval, onWheel}) => {
|
||||
))}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default ArchiveDisplay
|
||||
|
@ -1,11 +1,11 @@
|
||||
/* 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 LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DatePickerWrapper, makeDateSorter } from '@components/Table'
|
||||
import { PeriodPicker, defaultPeriod } from '@components/PeriodPicker'
|
||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||
import { TelemetryDataSaubService } from '@api'
|
||||
|
||||
import { normalizeData } from '@pages/TelemetryView'
|
||||
@ -69,7 +69,7 @@ export const Archive = memo(({ idWell }) => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [loaded, setLoaded] = useState(null)
|
||||
|
||||
const onGraphWheel = (e) => {
|
||||
const onGraphWheel = useCallback((e) => {
|
||||
if (loaded && dateLimit.from && dateLimit.to) {
|
||||
setStartDate((prevStartDate) => {
|
||||
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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [loaded, dateLimit, chartInterval])
|
||||
|
||||
const isDateDisabled = (date) => {
|
||||
const isDateDisabled = useCallback((date) => {
|
||||
if (!date) return false
|
||||
const dt = new Date(date).setHours(0, 0, 0, 0)
|
||||
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||
}
|
||||
const isDateTimeDisabled = (date) => ({
|
||||
}, [dateLimit])
|
||||
|
||||
const isDateTimeDisabled = useCallback((date) => ({
|
||||
disabledHours: () => range(0, 24).filter(h => {
|
||||
if (!date) return false
|
||||
const dt = +new Date(date).setHours(h)
|
||||
@ -102,7 +103,7 @@ export const Archive = memo(({ idWell }) => {
|
||||
const dt = +new Date(date).setSeconds(s)
|
||||
return dt < dateLimit.from || dt > +dateLimit.to - chartInterval
|
||||
})
|
||||
})
|
||||
}), [dateLimit])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
||||
|
||||
@ -108,7 +108,7 @@ export const ClusterWells = memo(({ statsWells }) => {
|
||||
setTableData(data)
|
||||
}, [statsWells])
|
||||
|
||||
const columns = [
|
||||
const columns = useMemo(() => [
|
||||
makeTextColumn('скв №', 'caption', null, null,
|
||||
(_, item) => (
|
||||
<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>
|
||||
)) ?? '-',
|
||||
},
|
||||
]
|
||||
], [location.pathname])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Table } from 'antd'
|
||||
|
||||
import { makeTextColumn, makeNumericColumnPlanFact } from '@components/Table'
|
||||
import { getPrecision } from '@utils/functions'
|
||||
|
||||
export const WellOperationsTable = ({ wellOperations }) => {
|
||||
const columns = [
|
||||
makeTextColumn('Конструкция секции','sectionType'),
|
||||
makeTextColumn('Операция','operationName'),
|
||||
makeNumericColumnPlanFact('Глубина забоя', 'depth', null, null, getPrecision),
|
||||
makeNumericColumnPlanFact('Часы', 'durationHours', null, null, getPrecision),
|
||||
makeNumericColumnPlanFact('Комментарий', 'comment', null, null, (text) => text ?? '-')
|
||||
]
|
||||
const columns = [
|
||||
makeTextColumn('Конструкция секции', 'sectionType'),
|
||||
makeTextColumn('Операция', 'operationName'),
|
||||
makeNumericColumnPlanFact('Глубина забоя', 'depth', null, null, getPrecision),
|
||||
makeNumericColumnPlanFact('Часы', 'durationHours', null, null, getPrecision),
|
||||
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,
|
||||
sectionType: el.plan?.wellSectionTypeName ?? el.fact?.wellSectionTypeName,
|
||||
operationName: `${el.plan?.categoryName ?? el.fact?.categoryName ?? ''} ${' '}
|
||||
@ -23,7 +24,7 @@ export const WellOperationsTable = ({ wellOperations }) => {
|
||||
durationHoursFact: el.fact?.durationHours,
|
||||
commentPlan: el.plan?.comment ?? '-',
|
||||
commentFact: el.fact?.comment ?? '-'
|
||||
}))
|
||||
})), [wellOperations])
|
||||
|
||||
return (
|
||||
<Table
|
||||
@ -36,6 +37,6 @@ export const WellOperationsTable = ({ wellOperations }) => {
|
||||
tableName={'well_operations'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
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 { arrayOrDefault } from '@utils'
|
||||
@ -8,7 +8,7 @@ import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
|
||||
import ClusterWells from './ClusterWells'
|
||||
|
||||
export const Cluster = () => {
|
||||
export const Cluster = memo(() => {
|
||||
const { idCluster } = useParams()
|
||||
const [data, setData] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
@ -28,6 +28,6 @@ export const Cluster = () => {
|
||||
<ClusterWells statsWells={data} />
|
||||
</LoaderPortal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
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 { FileService } from '@api'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { UploadForm } from '@components/UploadForm'
|
||||
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 { formatDate } from '@utils'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
|
||||
const pageSize = 12
|
||||
const { RangePicker } = DatePicker
|
||||
@ -24,26 +23,11 @@ const columns = [
|
||||
{name}
|
||||
</Button>
|
||||
),
|
||||
}, {
|
||||
title: 'Дата загрузки',
|
||||
key: 'uploadDate',
|
||||
dataIndex: 'uploadDate',
|
||||
render: item => formatDate(item, false, 'DD MMM YYYY, HH:mm:ss'),
|
||||
}, {
|
||||
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}/>
|
||||
}
|
||||
},
|
||||
makeDateColumn('Дата загрузки', 'uploadDate'),
|
||||
makeNumericColumn('Размер', 'size', null, null, formatBytes),
|
||||
makeColumn('Автор', 'author', { render: item => <UserView user={item}/> }),
|
||||
makeColumn('Компания', 'company', { render: (_, record) => <CompanyView company={record?.author?.company}/> })
|
||||
]
|
||||
|
||||
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 [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) => {
|
||||
await FileService.delete(idWell, file.id)
|
||||
update()
|
||||
}
|
||||
|
||||
const hanleCompanySearch = (value, _) => setFilterCompanyName(value)
|
||||
const hanleFileNameSearch = (value, _) => setFilterFileName(value)
|
||||
|
||||
const mergedColumns = [...columns, ...(customColumns ?? [])]
|
||||
|
||||
const update = () => {
|
||||
const update = useCallback(() => {
|
||||
let begin = null
|
||||
let end = null
|
||||
if (filterDataRange?.length > 1) {
|
||||
@ -101,13 +77,15 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
||||
`Не удалось загрузить файлы по скважине "${idWell}"`,
|
||||
'Загрузка файла по скважине'
|
||||
)
|
||||
}
|
||||
}, [filterCompanyName, filterDataRange, filterFileName, idCategory, idWell, page])
|
||||
|
||||
useEffect(update, [idWell, idCategory, page, filterDataRange, filterCompanyName, filterFileName])
|
||||
useEffect(update, [update])
|
||||
useEffect(() => onChange?.(files), [files, onChange])
|
||||
|
||||
const companies = [...new Set(files.map(file => file.company))].filter(company => company)
|
||||
const filenames = [...new Set(files.map(file => file.name))].filter(name => name)
|
||||
const handleFileDelete = useMemo(() => hasPermission(`File.edit${idCategory}`) && (async (file) => {
|
||||
await FileService.delete(idWell, file.id)
|
||||
update()
|
||||
}), [idWell, idCategory, update])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
@ -124,7 +102,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
||||
<Search
|
||||
list={'listCompanies'}
|
||||
placeholder={'Фильтр по компании'}
|
||||
onSearch={hanleCompanySearch}
|
||||
onSearch={setFilterCompanyName}
|
||||
/>
|
||||
<datalist id={'listCompanies'}>
|
||||
{companies.map((company) => (
|
||||
@ -138,7 +116,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
||||
<Search
|
||||
list={'listFileNames'}
|
||||
placeholder={'Фильтр по имени файла'}
|
||||
onSearch={hanleFileNameSearch}
|
||||
onSearch={setFilterFileName}
|
||||
/>
|
||||
<datalist id={'listFileNames'}>
|
||||
{filenames.map((name) => (
|
||||
@ -154,7 +132,7 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
||||
url={uploadUrl}
|
||||
accept={accept}
|
||||
onUploadStart={() => setShowLoader(true)}
|
||||
onUploadComplete={handleUploadComplete}
|
||||
onUploadComplete={update}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -169,9 +147,9 @@ export const DocumentsTemplate = ({ idCategory, idWell, accept, headerChild, cus
|
||||
pagination={{
|
||||
...pagination,
|
||||
showSizeChanger: false,
|
||||
onChange: (page) => setPage(page),
|
||||
onChange: setPage,
|
||||
}}
|
||||
onRowDelete={hasPermission(`File.edit${idCategory}`) && handleFileDelete}
|
||||
onRowDelete={handleFileDelete}
|
||||
rowKey={(record) => record.id}
|
||||
tableName={tableName ?? `file_${idCategory}`}
|
||||
/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { join } from 'path'
|
||||
import { memo } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { Layout, Menu } from 'antd'
|
||||
import { FolderOutlined } from '@ant-design/icons'
|
||||
import { Switch, useParams } from 'react-router-dom'
|
||||
@ -25,7 +25,7 @@ export const documentCategories = [
|
||||
|
||||
export const MenuDocuments = memo(({ idWell }) => {
|
||||
const { category } = useParams()
|
||||
const root = `/well/${idWell}/document`
|
||||
const root = useMemo(() => `/well/${idWell}/document`, [idWell])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -2,7 +2,7 @@ import { Form, Select } from 'antd'
|
||||
import { FileAddOutlined } from '@ant-design/icons'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import Poprompt from '@components/Poprompt'
|
||||
import Poprompt from '@components/selectors/Poprompt'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DrillingProgramService } from '@api'
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { UserView } from '@components/views'
|
||||
@ -113,7 +113,7 @@ export const CategoryEditor = memo(({ idWell, visible, category, onClosed }) =>
|
||||
`Изменение статуса пользователя`
|
||||
), [users, idWell, category.idFileCategory])
|
||||
|
||||
const userColumns = [
|
||||
const userColumns = useMemo(() => [
|
||||
makeColumn('Пользователь', 'user', {
|
||||
sorter: (a, b) => (a?.user?.surname && b?.user?.surname) ? a.user.surname.localeCompare(b.user.surname) : 0,
|
||||
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])
|
||||
|
||||
|
@ -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 {
|
||||
DeleteOutlined,
|
||||
@ -6,11 +6,11 @@ import {
|
||||
TableOutlined,
|
||||
} from '@ant-design/icons'
|
||||
|
||||
import Poprompt from '@components/Poprompt'
|
||||
import { UserView } from '@components/views'
|
||||
import UploadForm from '@components/UploadForm'
|
||||
import DownloadLink from '@components/DownloadLink'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import Poprompt from '@components/selectors/Poprompt'
|
||||
import { formatBytes, invokeWebApiWrapperAsync, notify } from '@components/factory'
|
||||
import { DrillingProgramService } from '@api'
|
||||
import { formatDate } from '@utils'
|
||||
@ -44,7 +44,7 @@ export const CategoryRender = memo(({ idWell, partData, onUpdate, onEdit, onHist
|
||||
file // Информация о файле
|
||||
} = 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 [isDeleting, setIsDeleting] = useState(false)
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
ReloadOutlined,
|
||||
WarningOutlined,
|
||||
} 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 { downloadFile, formatBytes, invokeWebApiWrapperAsync } from '@components/factory'
|
||||
@ -52,11 +52,11 @@ export const DrillingProgram = memo(({ idWell }) => {
|
||||
parts,
|
||||
program,
|
||||
error,
|
||||
} = data
|
||||
} = useMemo(() => data, [data])
|
||||
|
||||
const stateId = idState ?? idStateUnknown
|
||||
const state = stateString[stateId]
|
||||
const StateIcon = state.icon
|
||||
const stateId = useMemo(() => idState ?? idStateUnknown, [idState])
|
||||
const state = useMemo(() => stateString[stateId], [stateId])
|
||||
const StateIcon = useMemo(() => state.icon, [state?.icon])
|
||||
|
||||
const updateData = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
@ -76,15 +76,15 @@ export const DrillingProgram = memo(({ idWell }) => {
|
||||
|
||||
useEffect(() => updateData(), [updateData])
|
||||
|
||||
const onCategoryEdit = (catId) => {
|
||||
const onCategoryEdit = useCallback((catId) => {
|
||||
setSelectedCategory(catId)
|
||||
setEditorVisible(!!catId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onCategoryHistory = (catId) => {
|
||||
const onCategoryHistory = useCallback((catId) => {
|
||||
setSelectedCategory(catId)
|
||||
setHistoryVisible(!!catId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onEditorClosed = useCallback(() => {
|
||||
setEditorVisible(false)
|
||||
|
@ -13,9 +13,10 @@ import '@styles/index.css'
|
||||
import Logo from '@images/Logo'
|
||||
|
||||
export const Login = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const handleLogin = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Modal } from 'antd'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { Table } from '@components/Table'
|
||||
import { formatDate } from '@utils'
|
||||
@ -18,21 +18,18 @@ const dateColumn = {
|
||||
}
|
||||
|
||||
export const InclinometryTable = memo(({ group, visible, onClose }) => {
|
||||
const [tableColumns, setTableColumns] = useState([])
|
||||
const [tableData, setTableData] = useState([])
|
||||
|
||||
useEffect(() => setTableColumns([
|
||||
const tableColumns = useMemo(() => [
|
||||
dateColumn,
|
||||
...(group?.columns?.map((column) => ({
|
||||
...column,
|
||||
title: v(column.title)
|
||||
})) ?? [])
|
||||
]), [group?.columns])
|
||||
], [group?.columns])
|
||||
|
||||
useEffect(() => setTableData(group?.values?.map(row => ({
|
||||
const tableData = useMemo(() => group?.values?.map(row => ({
|
||||
date: row.timestamp,
|
||||
...row.data
|
||||
}))), [group?.values])
|
||||
})), [group?.values])
|
||||
|
||||
return !group?.columns ? null : (
|
||||
<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 {
|
||||
CheckSquareOutlined,
|
||||
@ -23,23 +23,20 @@ import '@styles/measure.css'
|
||||
const createEditingColumns = (cols, renderDelegate) =>
|
||||
cols.map(col => ({ render: renderDelegate, ...col }))
|
||||
|
||||
const disabled = !hasPermission('Measure.edit')
|
||||
|
||||
export const MeasureTable = memo(({ idWell, group, updateMeasuresFunc, additionalButtons }) => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [displayedValues, setDisplayedValues] = useState({})
|
||||
const [editingColumns, setEditingColumns] = useState(group.columns)
|
||||
const [isTableEditing, setIsTableEditing] = useState(false)
|
||||
const [editingActionName, setEditingActionName] = useState('')
|
||||
const [data, setData] = useState([])
|
||||
|
||||
const [measuresForm] = Form.useForm()
|
||||
|
||||
useEffect(() => {
|
||||
let data = [group.defaultValue]
|
||||
if (group?.values?.length > 0)
|
||||
data = group.values
|
||||
setData(data)
|
||||
setDisplayedValues(data.at(-1))
|
||||
}, [group.defaultValue, group.values])
|
||||
const data = useMemo(() => group?.values?.length > 0 ? group.values : [group?.defaultValue], [group?.defaultValue, group?.values])
|
||||
|
||||
useEffect(() => setDisplayedValues(data.at(-1)), [data])
|
||||
|
||||
useEffect(() => {
|
||||
const switchableColumns = createEditingColumns(
|
||||
@ -64,17 +61,15 @@ export const MeasureTable = memo(({ idWell, group, updateMeasuresFunc, additiona
|
||||
`Не удалось удалить запись ${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 editingDisabled = disabled || !!displayedValues?.isDefaultData
|
||||
const deleteDisabled = !hasPermission('Measure.delete') || !!displayedValues?.isDefaultData
|
||||
|
||||
const editTable = (action) => {
|
||||
const editTable = useCallback((action) => {
|
||||
setEditingActionName(action)
|
||||
setIsTableEditing(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleSubmitMeasuresForm = async (formData) => await invokeWebApiWrapperAsync(
|
||||
const handleSubmitMeasuresForm = useCallback(async (formData) => await invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
measuresForm.validateFields()
|
||||
|
||||
@ -99,7 +94,7 @@ export const MeasureTable = memo(({ idWell, group, updateMeasuresFunc, additiona
|
||||
setShowLoader,
|
||||
`Не удалось добавить/изменить запись для скаважины "${idWell}"`,
|
||||
'Добавление/изменение записи по скважине'
|
||||
)
|
||||
), [displayedValues?.id, displayedValues?.timestamp, editingActionName, group.idCategory, idWell, measuresForm, updateMeasuresFunc])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { MessageService } from '@api'
|
||||
@ -65,6 +65,8 @@ const filterOptions = [
|
||||
{ value: 3, label: 'Информация' },
|
||||
]
|
||||
|
||||
const children = filterOptions.map((line) => <Option key={line.value}>{line.label}</Option>)
|
||||
|
||||
// Данные для таблицы
|
||||
export const Messages = memo(({ idWell }) => {
|
||||
const [messages, setMessages] = useState([])
|
||||
@ -75,9 +77,7 @@ export const Messages = memo(({ idWell }) => {
|
||||
const [searchString, setSearchString] = useState('')
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const children = filterOptions.map((line) => <Option key={line.value}>{line.label}</Option>)
|
||||
|
||||
const onChangeSearchString = (message) => setSearchString(message.length > 2 ? message : '')
|
||||
const onChangeSearchString = useCallback((message) => setSearchString(message.length > 2 ? message : ''), [])
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'moment/locale/ru'
|
||||
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 { ReportService } from '@api'
|
||||
@ -43,7 +43,7 @@ export const Report = memo(({ idWell }) => {
|
||||
const [pagesCount, setPagesCount] = useState(0)
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const handleReportCreation = async () => await invokeWebApiWrapperAsync(
|
||||
const handleReportCreation = useCallback(async () => await invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const taskId = await ReportService.createReport(
|
||||
idWell,
|
||||
@ -81,9 +81,11 @@ export const Report = memo(({ idWell }) => {
|
||||
${filterDateRange[0].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(
|
||||
async () => {
|
||||
|
@ -6,7 +6,7 @@ import { Grid, GridItem } from '@components/Grid'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
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 }) => {
|
||||
const [expirePeriod, setExpirePeriod] = useState(defaultPeriod)
|
||||
|
@ -12,7 +12,7 @@ import { makeDateSorter } from '@components/Table'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { Grid, GridItem, Flex } from '@components/Grid'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { PeriodPicker, defaultPeriod } from '@components/PeriodPicker'
|
||||
import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { Subscribe } from '@services/signalr'
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { memo } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import {
|
||||
FolderOutlined,
|
||||
FundViewOutlined,
|
||||
@ -30,7 +30,7 @@ const { Content } = Layout
|
||||
|
||||
export const Well = memo(() => {
|
||||
const { idWell, tab } = useParams()
|
||||
const rootPath = `/well/${idWell}`
|
||||
const rootPath = useMemo(() => `/well/${idWell}`, [idWell])
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
@ -12,8 +12,8 @@ const style = { margin: 4 }
|
||||
export const ImportExportBar = memo(({ idWell, onImported, disabled }) => {
|
||||
const [isImportModalVisible, setIsImportModalVisible] = useState(false)
|
||||
|
||||
const downloadTemplate = async () => download(`/api/well/${idWell}/wellOperations/template`)
|
||||
const downloadExport = async () => download(`/api/well/${idWell}/wellOperations/export`)
|
||||
const downloadTemplate = async () => await download(`/api/well/${idWell}/wellOperations/template`)
|
||||
const downloadExport = async () => await download(`/api/well/${idWell}/wellOperations/export`)
|
||||
|
||||
const onDone = () => {
|
||||
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 {
|
||||
EditableTable,
|
||||
@ -54,15 +54,15 @@ export const WellDrillParams = memo(({ idWell }) => {
|
||||
await updateParams()
|
||||
})(), [idWell, updateParams])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: DrillParamsService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateParams,
|
||||
idWell
|
||||
}
|
||||
}), [idWell, updateParams])
|
||||
|
||||
const recordParser = (record) => ({ ...record, idWell })
|
||||
const recordParser = useCallback((record) => ({ ...record, idWell }), [idWell])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
|
@ -1,86 +1,88 @@
|
||||
import moment from 'moment'
|
||||
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 {
|
||||
EditableTable,
|
||||
DatePickerWrapper,
|
||||
makeColumn,
|
||||
makeDateSorter,
|
||||
makeNumericColumnOptions,
|
||||
makeSelectColumn,
|
||||
makeActionHandler,
|
||||
makeDateColumn,
|
||||
makeNumericColumn,
|
||||
makeNumericRender,
|
||||
makeNumericSorter,
|
||||
makeTextColumn,
|
||||
} from '@components/Table'
|
||||
import { WellOperationService} from '@api'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { hasPermission } from '@utils/permissions'
|
||||
import { formatDate } from '@utils'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { WellOperationService } from '@api'
|
||||
|
||||
const { TextArea } = Input
|
||||
|
||||
const basePageSize = 160
|
||||
const dayRender = makeNumericRender(2)
|
||||
const dayWithoutNptRender = (_, row) => dayRender((row.day ?? 0) - (row.nptHours ?? 0) / 24)
|
||||
|
||||
const defaultColumns = [
|
||||
makeSelectColumn('Конструкция секции', 'idWellSectionType', [], undefined, {
|
||||
const generateColumns = (showNpt = false, categories = [], sectionTypes = []) => [
|
||||
makeSelectColumn('Конструкция секции', 'idWellSectionType', sectionTypes, undefined, {
|
||||
sorter: makeNumericSorter('idWellSectionType'),
|
||||
editable: true,
|
||||
width: 160,
|
||||
formItemRules: [({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (value?.length > 0)
|
||||
return Promise.resolve()
|
||||
return Promise.reject('Это обязательное поле!')
|
||||
}
|
||||
})],
|
||||
}),
|
||||
makeSelectColumn('Операция', 'idCategory', [], undefined, { editable: true, width: 200 }),
|
||||
makeColumn('Доп. инфо', 'categoryInfo', { editable: true, width: 300, input: <TextArea/> }),
|
||||
makeColumn('Глубина забоя на начало, м', 'depthStart', makeNumericColumnOptions(2, 'depthStart')),
|
||||
makeColumn('Глубина забоя при завершении, м', 'depthEnd', makeNumericColumnOptions(2, 'depthEnd')),
|
||||
makeColumn('Время начала', 'dateStart', {
|
||||
makeSelectColumn('Операция', 'idCategory', categories, undefined, {
|
||||
sorter: makeNumericSorter('idCategory'),
|
||||
editable: true,
|
||||
width: 200,
|
||||
input: <DatePickerWrapper/>,
|
||||
initialValue: moment().format(),
|
||||
sorter: makeDateSorter('dateStart'),
|
||||
render: (_, record) => (
|
||||
<div className={'text-align-r-container'}>
|
||||
<span>{formatDate(record.dateStart)}</span>
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
makeColumn('Часы', 'durationHours', makeNumericColumnOptions(2, 'durationHours')),
|
||||
makeColumn('Комментарий', 'comment', { editable: true, input: <TextArea/> }),
|
||||
]
|
||||
makeTextColumn('Доп. инфо', 'categoryInfo', null, null, null, { editable: true, width: 300, 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 }) => {
|
||||
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({current:1, pageSize:basePageSize})
|
||||
export const WellOperationsEditor = memo(({ idWell, idType, showNpt, ...other }) => {
|
||||
const [pageNumAndPageSize, setPageNumAndPageSize] = useState({ current: 1, pageSize: basePageSize })
|
||||
const [paginationTotal, setPaginationTotal] = useState(0)
|
||||
const [operations, setOperations] = useState([])
|
||||
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(
|
||||
async () => {
|
||||
let categories = await WellOperationService.getCategories(idWell) ?? []
|
||||
categories = categories.map((item) => ({ value: item.id, label: item.name }))
|
||||
const categories = arrayOrDefault(await WellOperationService.getCategories(idWell))
|
||||
setCategories(categories.map((item) => ({ value: item.id, label: item.name })))
|
||||
|
||||
let sectionTypes = await WellOperationService.getSectionTypes(idWell) ?? []
|
||||
sectionTypes = Object.keys(sectionTypes).map((key) => ({ value: parseInt(key), label: sectionTypes[key] }))
|
||||
|
||||
setColumns(preColumns => {
|
||||
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
|
||||
})
|
||||
const sectionTypes = Object.entries(await WellOperationService.getSectionTypes(idWell) ?? {})
|
||||
setSectionTypes(sectionTypes.map(([id, label]) => ({ value: parseInt(id), label })))
|
||||
},
|
||||
setShowLoader,
|
||||
'Не удалось загрузить список операций по скважине',
|
||||
'Получение списка операций по скважине'
|
||||
), [idWell])
|
||||
|
||||
const updateOperations = () => invokeWebApiWrapperAsync(
|
||||
const updateOperations = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const skip = ((pageNumAndPageSize.current - 1) * pageNumAndPageSize.pageSize) || 0
|
||||
const take = pageNumAndPageSize.pageSize
|
||||
@ -95,17 +97,22 @@ export const WellOperationsEditor = memo(({ idWell, idType, ...other }) => {
|
||||
setShowLoader,
|
||||
'Не удалось загрузить список операций по скважине',
|
||||
'Получение списка операций по скважине'
|
||||
)
|
||||
), [idWell, idType, pageNumAndPageSize])
|
||||
|
||||
useEffect(updateOperations, [idWell, idType, pageNumAndPageSize])
|
||||
useEffect(updateOperations, [updateOperations])
|
||||
|
||||
const handlerProps = {
|
||||
const handlerProps = useMemo(() => ({
|
||||
service: WellOperationService,
|
||||
setLoader: setShowLoader,
|
||||
errorMsg: `Не удалось выполнить операцию`,
|
||||
onComplete: updateOperations,
|
||||
idWell
|
||||
}
|
||||
}), [idWell, updateOperations])
|
||||
|
||||
const onRow = useCallback((record) => {
|
||||
if (selectedIds?.includes(record.id))
|
||||
return { style: { background: '#BF0000A0' } }
|
||||
}, [selectedIds])
|
||||
|
||||
const recordParser = (record) => ({
|
||||
...record,
|
||||
@ -133,6 +140,7 @@ export const WellOperationsEditor = memo(({ idWell, idType, ...other }) => {
|
||||
onChange: (page, pageSize) => setPageNumAndPageSize({ current: page, pageSize })
|
||||
}}
|
||||
tableName={'well_operationse_editor'}
|
||||
onRow={onRow}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
)
|
||||
|
@ -59,7 +59,7 @@ export const WellOperations = memo(({ idWell }) => {
|
||||
<WellOperationsEditor idWell={idWell} idType={0} tableName={'well_operations_plan'}/>
|
||||
</PrivateRoute>
|
||||
<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 path={`${rootPath}/drillProcessFlow`}>
|
||||
<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 const defaultFormat: string = 'YYYY.MM.DD HH:mm'
|
||||
export const defaultFormat: string = 'DD.MM.YYYY HH:mm'
|
||||
|
||||
export enum timeInS {
|
||||
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
|
||||
}
|
||||
|
||||
export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate => {
|
||||
if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return NaN
|
||||
export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate | null => {
|
||||
if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return null
|
||||
const d = new Date(date)
|
||||
d.setMilliseconds(d.getMilliseconds() + value * timeInS[type] * 1000)
|
||||
return d
|
||||
|
@ -7,28 +7,37 @@ export const getPrecision = (number: number) => Number.isFinite(number) ? number
|
||||
|
||||
export type KeyType = number | string
|
||||
|
||||
export type SaubData = {
|
||||
key?: number
|
||||
depth?: number
|
||||
date?: string
|
||||
day?: number
|
||||
export const nwtIdCategory = 1043
|
||||
|
||||
export type SaubData = WellOperationDto & {
|
||||
isNPT?: boolean // Относится ли операция к НПВ
|
||||
key?: number // ID операции
|
||||
depth?: number // Глубина
|
||||
date?: string // Дата
|
||||
nptHours?: number // Колличество часов НПВ с начала бурения до текущего момента
|
||||
}
|
||||
|
||||
export const getOperations = async (idWell: number): Promise<{
|
||||
operations?: WellOperationDtoPlanFactPredictBase[],
|
||||
plan?: SaubData[]
|
||||
fact?: SaubData[]
|
||||
predict?: SaubData[]
|
||||
operations: WellOperationDtoPlanFactPredictBase[],
|
||||
plan: SaubData[]
|
||||
fact: SaubData[]
|
||||
predict: SaubData[]
|
||||
}> => {
|
||||
const ops = await OperationStatService.getTvd(idWell)
|
||||
|
||||
if (!ops) return {}
|
||||
if (!ops) return {
|
||||
operations: [],
|
||||
plan: [],
|
||||
fact: [],
|
||||
predict: [],
|
||||
}
|
||||
|
||||
const convert = (operation?: WellOperationDto): SaubData => ({
|
||||
...operation,
|
||||
key: operation?.id,
|
||||
depth: operation?.depthStart,
|
||||
date: operation?.dateStart,
|
||||
day: operation?.day,
|
||||
isNPT: operation?.idCategory === nwtIdCategory,
|
||||
})
|
||||
|
||||
const planData = ops
|
||||
|
@ -78,7 +78,10 @@ export const requirements: PermissionRecord = {
|
||||
permission: ['AdminPermission.get'],
|
||||
role: ['AdminUserRole.get', 'AdminPermission.get'],
|
||||
visit_log: ['RequerstTracker.get'],
|
||||
telemetry: ['AdminTelemetry.get'],
|
||||
telemetry: {
|
||||
merger: ['AdminTelemetry.get'],
|
||||
viewer: ['AdminTelemetry.get'],
|
||||
},
|
||||
},
|
||||
deposit: ['Deposit.get', 'Cluster.get'],
|
||||
cluster: {
|
||||
|
Loading…
Reference in New Issue
Block a user