Compare commits

...

28 Commits

Author SHA1 Message Date
goodmice
609cd2cf45
Выделен файл под миксины 2022-12-28 18:23:02 +05:00
goodmice
a6fa931b04
Изменены основные цвета и цвета алертов 2022-12-28 18:22:39 +05:00
goodmice
ea44e8adc2
Исправлен размер колонок страницы Сообщения 2022-12-28 18:21:50 +05:00
goodmice
6a81db11b6
Обновлены стили страницы "Статистика использования уставок" 2022-12-28 18:21:31 +05:00
goodmice
842e1773b4
Основные стили обновлены 2022-12-28 17:06:22 +05:00
goodmice
cf87354cd1
Обновлены стили компонентов Segmented, Select и DatePicker 2022-12-28 16:56:51 +05:00
goodmice
b1b4283bc5
Обновлены стили селектора скважины 2022-12-28 16:56:03 +05:00
goodmice
5b4ba6c6f2
Обновлены стили загрузки 2022-12-28 16:55:19 +05:00
goodmice
b7ceee2fca
Обновлены иконки на Мониторинге 2022-12-28 16:53:35 +05:00
goodmice
1c56368435
Исправлена страница Сообщения 2022-12-28 12:20:16 +05:00
goodmice
0f2251b8d0
Исправлена страница Дашборда ННБ 2022-12-28 12:19:46 +05:00
goodmice
e9cc5f14e7
Исправлена страница входа и регистрации 2022-12-28 12:18:32 +05:00
goodmice
1ba9ce59db
Merge branch 'dev' into feature/new-design 2022-12-28 11:39:58 +05:00
goodmice
85e12263cd
Merge branch 'dev' into feature/new-design 2022-12-28 11:38:05 +05:00
goodmice
c834db3048
Начата переработка Мониторинга 2022-12-27 19:34:31 +05:00
goodmice
f4749094b1
Иконки скважин оптимизированы для тёмной темы 2022-12-27 19:18:14 +05:00
goodmice
e417f93a61
Тема сменена на тёмную 2022-12-27 19:17:37 +05:00
goodmice
31b97855e8
Перемещён селектор цвета 2022-12-27 19:04:23 +05:00
goodmice
1e31f2f2c1
Отображение списков тегов обновлено 2022-12-27 19:04:02 +05:00
goodmice
cbbe992966
Исправлено отображение тайлов на карте 2022-12-26 18:37:26 +05:00
goodmice
ad59f4b840
Удалены ненужные изображения 2022-12-26 18:28:00 +05:00
goodmice
e3ecf7f8f3
Перемещены изображения страницы "Мониторинг" 2022-12-26 18:27:31 +05:00
goodmice
1885a48d06
Перемещены изображения селектора скважин 2022-12-26 18:26:58 +05:00
goodmice
7a242ab59c
Переработан вид страницы "Карта" 2022-12-26 17:58:46 +05:00
goodmice
dde32f737a
Добавлены новые иконки на страницу карты 2022-12-26 17:58:27 +05:00
goodmice
b3954c1091
Добавлен метод окрытия селектора скважин в LayoutPropsContext 2022-12-26 17:57:56 +05:00
goodmice
a01d0fb095
Добавлен новый основной шрифт 2022-12-26 17:57:09 +05:00
goodmice
92526907fa
IP dev-сервера обновлены 2022-12-26 17:56:22 +05:00
73 changed files with 663 additions and 452 deletions

View File

@ -24,7 +24,7 @@
"dev": "webpack-dev-server --env=\"ENV=dev\" --open --hot",
"oul": "npx openapi -i http://127.0.0.1:5000/swagger/v1/swagger.json -o src/services/api",
"oud": "npx openapi -i http://192.168.1.10:5000/swagger/v1/swagger.json -o src/services/api",
"oud": "npx openapi -i http://192.168.0.10:5000/swagger/v1/swagger.json -o src/services/api",
"oug": "npx openapi -i https://cloud.digitaldrilling.ru/swagger/v1/swagger.json -o src/services/api",
"oug_dev": "npx openapi -i http://46.146.207.184/swagger/v1/swagger.json -o src/services/api"
},

View File

@ -7,7 +7,7 @@ import { downloadFile } from './factory'
import { getLinkToFile } from '@pages/FileDownload'
import '@styles/index.css'
import '@styles/components/download_link.less'
export type DownloadLinkProps = LinkProps & {
file?: FileInfoDto

View File

@ -1,4 +1,6 @@
import React, { HTMLAttributes, memo } from 'react'
import { HTMLAttributes, memo } from 'react'
import '@styles/components/grid.less'
export type ComponentProps = HTMLAttributes<HTMLDivElement>
@ -36,7 +38,7 @@ export const GridItem = memo<GridItemProps>(({ children, row, col, rowSpan, colS
}
return (
<div className={`asb-grid-item ${className || ''}`} style={gridItemStyle} {...other}>
<div className={`dd-grid-item ${className || ''}`} style={gridItemStyle} {...other}>
{children}
</div>
)

View File

@ -55,6 +55,8 @@ const _LayoutPortal = memo(() => {
const setLayoutProps = useCallback((props: LayoutPortalProps) => setProps({ ...defaultProps, ...props}), [])
const layoutPropsValue = useMemo(() => ({ setLayoutProps, openWellTreeSelector: () => setWellsTreeOpen(true) }), [setLayoutProps])
useEffect(() => {
if (typeof showSelector === 'boolean')
setWellsTreeOpen(showSelector)
@ -72,7 +74,7 @@ const _LayoutPortal = memo(() => {
return (
<Layout className={`page-layout ${isAdmin ? 'page-layout-admin' : ''}`}>
{(sider || siderProps) && (
<Sider {...siderProps} collapsedWidth={50} collapsed={menuCollapsed} trigger={null} collapsible className={`menu-sider ${siderProps?.className || ''}`}>
<Sider {...siderProps} collapsedWidth={50} collapsed={menuCollapsed} trigger={null} collapsible className={`dd-menu-sider ${siderProps?.className || ''}`}>
<div className={'sider-content'}>
<button className={'sider-toogle'} onClick={() => setMenuCollapsed((prev) => !prev)}>
<Logo onlyIcon={menuCollapsed} />
@ -123,7 +125,7 @@ const _LayoutPortal = memo(() => {
{topRightBlock}
</div>
)}
<LayoutPropsContext.Provider value={setLayoutProps}>
<LayoutPropsContext.Provider value={layoutPropsValue}>
<Suspense fallback={fallback ?? <SuspenseFallback style={{ minHeight: '100%' }} />}>
<Outlet />
</Suspense>

View File

@ -1,7 +1,8 @@
import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
import { Select, SelectProps, Tag } from 'antd'
import { Select, SelectProps } from 'antd'
import TagList from '@components/TagList'
import type { OmitExtends } from '@utils/types'
import { ColumnProps, DataType, makeColumn } from '.'
@ -67,7 +68,9 @@ export const makeTagColumn = <T extends DataType>(
return makeColumn(title, dataIndex, {
editable: true,
...other,
render: (item: T[] | undefined, dataset, index) => item?.map((elm: T) => <Tag key={elm[label_key]} color={'blue'}>{other?.render?.(elm, dataset, index) ?? elm[label_key]}</Tag>) ?? '-',
render: (item: T[] | undefined, dataset, index) => (
<TagList items={item} render={(elm) => other?.render?.(elm, dataset, index) ?? elm[label_key]} />
),
input: <InputComponent {...tagOther} options={options} />,
})
}

View File

@ -8,7 +8,7 @@ import TableSettingsChanger from './TableSettingsChanger'
import type { OmitExtends } from '@utils/types'
import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils'
import '@styles/index.css'
import '@styles/components/table.less'
export type BaseTableColumn<T> = ColumnGroupType<T> | ColumnType<T>
export type TableColumn<T> = OmitExtends<BaseTableColumn<T>, TableColumnSettings>

View File

@ -0,0 +1,25 @@
import { memo, ReactNode } from 'react'
import { Tag } from 'antd'
import '@styles/components/tag_list.less'
export type TagListProps<T> = {
items?: T[] | null
render: (item: T) => ReactNode
}
const _TagList = <T,>({ items, render }: TagListProps<T>) => {
if (!items || items.length <= 0) return <>-</>
return (
<div className={'dd-tag-list'}>
{items.map((item, i) => (
<Tag key={`${i}`}>{render(item)}</Tag>
))}
</div>
)
}
export const TagList = memo(_TagList) as typeof _TagList
export default TagList

View File

@ -2,7 +2,7 @@ import { Button, Checkbox, Form, FormItemProps, Input, InputNumber, Select, Tool
import { memo, useCallback, useEffect, useMemo } from 'react'
import { BaseDataType, MinMax } from '@components/d3/types'
import { ColorPicker, Color } from '@components/ColorPicker'
import { ColorPicker, Color } from '@components/input/ColorPicker'
import { ExtendedChartDataset } from './D3MonitoringCharts'

View File

@ -545,7 +545,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
<g ref={setChartAreaRef} className={'chart-area'} transform={`translate(${offset.left}, ${sizes.chartsTop})`}>
<rect width={sizes.inlineWidth} height={sizes.chartsHeight} fill={backgroundColor} />
</g>
<g stroke={'black'}>
<g stroke={'white'}>
{d3.range(1, groups.length).map((i) => {
const x = offset.left + (sizes.groupWidth + spaceBetweenGroups) * i - spaceBetweenGroups / 2
return <line key={`${i}`} x1={x} x2={x} y1={sizes.chartsTop} y2={offset.top + sizes.inlineHeight} />

View File

@ -16,10 +16,10 @@ export interface PointerIconProps {
}
const defaultColors: PointerIconColors = {
online: 'red',
active: 'black',
inactive: 'gray',
unknown: 'gray',
online: '#C32828',
active: 'white',
inactive: '#A7A7A7',
unknown: '#7B7B7B',
}
const defaultProps: PointerIconProps = {

View File

@ -16,10 +16,10 @@ export type WellIconProps = React.SVGProps<SVGSVGElement> & {
}
const defaultColors: WellIconColors = {
online: 'red',
active: 'black',
inactive: 'gray',
unknown: 'gray',
online: 'white',
active: 'white',
inactive: '#A7A7A7',
unknown: '#7B7B7B',
}
const defaultProps: WellIconProps = {
@ -34,7 +34,7 @@ export const WellIcon = React.memo(({ width, height, state, online, colors, ...o
return (
<svg
version={'1.1'}
viewBox={'12 .64 9.2 9.2'}
viewBox={'12 0 9 9'}
xmlns={'http://www.w3.org/2000/svg'}
xmlnsXlink={'http://www.w3.org/1999/xlink'}
@ -46,17 +46,15 @@ export const WellIcon = React.memo(({ width, height, state, online, colors, ...o
width={width}
height={height}
color={colors[state ?? 'unknown']}
color={colors[state || 'unknown']}
{...other}
>
<g>
<path d={'m12 7.9 2.6-6.6v-1.3h1.3v1.3l2.6 6.6'}/>
<path d={'m12 6.4h6.6v-0.53h-6.6z'} fill={'currentColor'}/>
<path d={'m14 0.79v0.53h2.9v-0.53z'} fill={'currentColor'}/>
<path d={'m12 7.9 5-4-2.4-2.6h1.3l-2.4 2.6 5 4'}/>
</g>
<path d={'m12 7.9 2.6-6.6v-1.3h1.3v1.3l2.6 6.6'}/>
<path d={'m12 6.4h6.6v-0.53h-6.6z'} fill={'currentColor'}/>
<path d={'m14 0.79v0.53h2.9v-0.53z'} fill={'currentColor'}/>
<path d={'m12 7.9 5-4-2.4-2.6h1.3l-2.4 2.6 5 4'}/>
{online && ( // Полоски, показывающие наличие свежей телеметрии
<g stroke={colors.online}>
<g stroke={colors.online} strokeWidth={.4}>
<path d={'m18.4 0.0662a2 2 0 0 1 0.141 1.7 2 2 0 0 1-1.22 1.19'} />
<path d={'m19.5 0.0402a3 3 0 0 1-1.79 3.85'} />
<path d={'m20.5 0.031a4 4 0 0 1-2.5 4.79'} />

View File

@ -2,7 +2,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { Input, Popover, Slider } from 'antd'
import { CopyOutlined } from '@ant-design/icons'
import { copyToClipboard } from './factory'
import { copyToClipboard } from '@components/factory'
import '@styles/components/color_picker.less'

View File

@ -7,10 +7,10 @@ import { WellIcon, WellIconState } from '@components/icons'
import { DepositDto, WellDto } from '@api'
import { isRawDate } from '@utils'
import { ReactComponent as DepositIcon } from '@images/DepositIcon.svg'
import { ReactComponent as ClusterIcon } from '@images/ClusterIcon.svg'
import { ReactComponent as DepositIcon } from '@images/components/well_tree_selector/DepositIcon.svg'
import { ReactComponent as ClusterIcon } from '@images/components/well_tree_selector/ClusterIcon.svg'
import '@styles/components/well_tree_select.css'
import '@styles/components/well_tree_select.less'
/**
* Для поиска в URL текущего раздела по шаблону `/{type}/{id}`
@ -175,6 +175,7 @@ export const WellTreeSelector = memo<WellTreeSelectorProps>(({ expand, current,
<Drawer open={open} mask={false} onClose={onClose} title={'Список скважин'}>
<Tree
{...other}
className={'well-tree-selector'}
showIcon
selectedKeys={selected}
treeData={wellsTree}

View File

@ -1,9 +1,10 @@
import { memo } from 'react'
import { Tag, Tooltip } from 'antd'
import { Tooltip } from 'antd'
import { UserRoleDto } from '@api'
import { Grid, GridItem } from '@components/Grid'
import PermissionView from './PermissionView'
import TagList from '../TagList'
export type RoleViewProps = {
role?: UserRoleDto
@ -26,12 +27,8 @@ export const RoleView = memo<RoleViewProps>(({ role }) => {
<GridItem row={2} col={1}>Включённые роли:</GridItem>
{hasIncludedRoles ? (
<GridItem row={3} col={1} colSpan={3}>
{role.roles?.map((role, i) => (
<Tag key={i} color={'blue'}>
<RoleView role={role} />
</Tag>
))}
<GridItem row={3} col={1} colSpan={3} style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
<TagList items={role.roles} render={(role) => <RoleView role={role} />} />
</GridItem>
) : (
<GridItem row={2} col={2}>Отсутствуют</GridItem>
@ -42,12 +39,8 @@ export const RoleView = memo<RoleViewProps>(({ role }) => {
<GridItem row={4 + hasIncludedRoles} col={1}>Разрешения:</GridItem>
{hasPermissions ? (
<GridItem row={5 + hasIncludedRoles} col={1} colSpan={3}>
{role.permissions?.map((permission, i) => (
<Tag key={i} color={'blue'}>
<PermissionView info={permission} />
</Tag>
))}
<GridItem row={5 + hasIncludedRoles} col={1} colSpan={3} style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
<TagList items={role.permissions} render={(permission) => <PermissionView info={permission} />} />
</GridItem>
) : (
<GridItem row={4 + hasIncludedRoles} col={2}>Отсутствуют</GridItem>

View File

@ -28,9 +28,9 @@ export const defaultSettings: WidgetSettings = {
label: 'Виджет',
formatter: defaultFormatter,
labelColor: '#000000',
valueColor: '#000000',
backgroundColor: '#f6f6f6',
labelColor: '#FFFFFF',
valueColor: '#FFFFFF',
backgroundColor: '#2F2F2F',
unitColor: '#a0a0a0',
}

View File

@ -2,22 +2,30 @@ import { createContext, useContext, useEffect } from 'react'
import { LayoutPortalProps } from '@components/LayoutPortal'
export type LayoutPropsContext = {
setLayoutProps: (props: LayoutPortalProps) => void
openWellTreeSelector: () => void
}
/** Контекст метода редактирования параметров заголовка и меню */
export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {})
export const LayoutPropsContext = createContext<LayoutPropsContext>({
setLayoutProps: () => {},
openWellTreeSelector: () => {},
})
/**
* Получить метод задания параметров заголовка и меню
*
* @returns Получить метод задания параметров заголовка и меню
*/
export const useLayoutProps = (props?: LayoutPortalProps) => {
const setLayoutProps = useContext(LayoutPropsContext)
export const useLayoutProps = (props?: LayoutPortalProps): LayoutPropsContext => {
const { setLayoutProps, ...other } = useContext(LayoutPropsContext)
useEffect(() => {
if (props) setLayoutProps(props)
}, [setLayoutProps, props])
return setLayoutProps
return { setLayoutProps, ...other }
}
/** Контекст для блока справа от крошек на страницах скважин и админки */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

View File

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 729 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,49 @@
import { memo, useMemo } from 'react'
import '@styles/components/icons/cluster-map-icon.less'
export type ClusterMapIconProps = {
width?: number | string
height?: number | string
count?: number
showBadge?: boolean
}
const defaultWidth = 65
const defaultHeight = 73
const ratio = defaultWidth / defaultHeight
export const ClusterMapIcon = memo<ClusterMapIconProps>(({ width, height, count, showBadge }) => {
const { w, h } = useMemo(() => {
const hasW = typeof width !== 'undefined' && width !== null
const hasH = typeof height !== 'undefined' && height !== null
const out: Record<string, string | number> = { w: defaultWidth, h: defaultHeight }
if (hasW) out.w = width
else if (typeof height === 'number') out.w = height / ratio
if (hasH) out.h = height
else if (typeof width === 'number') out.h = width * ratio
return out
}, [width, height])
return (
<svg className={'dd-cluster-map-icon'} overflow={'visible'} width={w} height={h} viewBox={'0 0 65 73'} xmlns={'http://www.w3.org/2000/svg'} fill={'white'}>
<path d={'M6 51.0897V21.9103L32.5 6.2024L59 21.9103V51.0897L32.5 66.7976L6 51.0897Z'} />
<g fill-rule={'evenodd'} clip-rule={'evenodd'}>
<path d={'M6 21.9103V51.0897L32.5 66.7976L59 51.0897V21.9103L32.5 6.2024L6 21.9103ZM59.6088 50.7288L59.6073 50.7297L59.6088 50.7288ZM5.39116 22.2712L5.39262 22.2703L5.39116 22.2712ZM0 51.7966V21.2034C0 19.5226 0.88635 17.9666 2.33174 17.1098L30.0756 0.664577C31.5705 -0.221525 33.4295 -0.221526 34.9244 0.664576L62.6683 17.1098C64.1137 17.9666 65 19.5227 65 21.2034V51.7966C65 53.4774 64.1137 55.0334 62.6683 55.8902L34.9244 72.3354C33.4295 73.2215 31.5705 73.2215 30.0756 72.3354L2.33174 55.8902C0.88635 55.0334 0 53.4774 0 51.7966Z'} />
<g fill={'#306AFD'}>
<path d={'M12 26.8557V46.1444C12 47.2041 12.5591 48.1852 13.4708 48.7254L30.9708 59.094C31.9137 59.6527 33.0863 59.6527 34.0292 59.094L51.5292 48.7254C52.4409 48.1852 53 47.2041 53 46.1444V26.8557C53 25.7959 52.4409 24.8148 51.5292 24.2747L34.0292 13.9061C33.0863 13.3474 31.9137 13.3474 30.9708 13.9061L13.4708 24.2747C12.5591 24.8148 12 25.7959 12 26.8557Z'} />
<path d={'M18 28.5652V44.4348L32.5 53.026L47 44.4348V28.5652L32.5 19.9741L18 28.5652ZM34.0292 19.0681C34.0286 19.0684 34.028 19.0688 34.0274 19.0691L34.0292 19.0681ZM12 46.1444V26.8557C12 25.7959 12.5591 24.8148 13.4708 24.2747L30.9708 13.9061C31.9137 13.3474 33.0863 13.3474 34.0292 13.9061L51.5292 24.2747C52.4409 24.8148 53 25.7959 53 26.8557V46.1444C53 47.2041 52.4409 48.1852 51.5292 48.7254L34.0292 59.094C33.0863 59.6527 31.9137 59.6527 30.9708 59.094L13.4708 48.7254C12.5591 48.1852 12 47.2041 12 46.1444Z'} />
<path d={'M6 21.9103V51.0897L32.5 66.7976L59 51.0897V21.9103L32.5 6.2024L6 21.9103ZM59.6088 50.7288L59.6073 50.7297L59.6088 50.7288ZM5.39116 22.2712L5.39262 22.2703L5.39116 22.2712ZM0 51.7966V21.2034C0 19.5226 0.88635 17.9666 2.33174 17.1098L30.0756 0.664577C31.5705 -0.221525 33.4295 -0.221526 34.9244 0.664576L62.6683 17.1098C64.1137 17.9666 65 19.5227 65 21.2034V51.7966C65 53.4774 64.1137 55.0334 62.6683 55.8902L34.9244 72.3354C33.4295 73.2215 31.5705 73.2215 30.0756 72.3354L2.33174 55.8902C0.88635 55.0334 0 53.4774 0 51.7966Z'} />
</g>
</g>
{typeof count === 'number' && (
<text className={'dd-cluster-map-icon-count'} textAnchor={'middle'} dominantBaseline={'middle'} style={{ fontSize: 22 }} x={'50%'} y={'50%'}>{count}</text>
)}
{showBadge && (
<circle className={'dd-cluster-map-icon-badge'} cx={59} cy={20} r={10.5} fill={'#FF2323'} stroke={'white'} stroke-width={'3'} />
)}
</svg>
)
})
export default ClusterMapIcon

View File

@ -0,0 +1,3 @@
<svg width="16" height="20" viewBox="0 0 16 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00016 0.833252C3.70375 0.833252 0.208496 4.3285 0.208496 8.62492C0.208496 10.4748 0.872621 12.27 2.08445 13.6886C2.22287 13.8458 5.4885 17.5565 6.57933 18.5964C6.97762 18.9764 7.48866 19.1666 8.00016 19.1666C8.51166 19.1666 9.0227 18.9764 9.42145 18.5964C10.6897 17.3869 13.7866 13.8366 13.9223 13.6808C15.1277 12.27 15.7918 10.4748 15.7918 8.62492C15.7918 4.3285 12.2966 0.833252 8.00016 0.833252ZM8.00016 10.9166C6.7347 10.9166 5.7085 9.89038 5.7085 8.62492C5.7085 7.35946 6.7347 6.33325 8.00016 6.33325C9.26562 6.33325 10.2918 7.35946 10.2918 8.62492C10.2918 9.89038 9.26562 10.9166 8.00016 10.9166Z"/>
</svg>

After

Width:  |  Height:  |  Size: 729 B

View File

@ -0,0 +1,10 @@
<svg width="44" height="49" viewBox="0 0 44 49" fill="white" xmlns="http://www.w3.org/2000/svg">
<path d="M39.9385 12.0881V21.7588L30.1374 33.5092L22 43.4487L13.8626 33.5092L4.06154 21.7588V12.0881L12.3728 4.06154H31.6272L39.9385 12.0881Z"/>
<g fill-rule="evenodd" clip-rule="evenodd">
<path d="M13.8626 33.5092L22 43.4487L30.1374 33.5092L39.9385 21.7588V12.0881L31.6272 4.06154H12.3728L4.06154 12.0881V21.7588L13.8626 33.5092ZM33.2683 36.0965L24.4356 46.8852C23.1516 48.4536 20.8484 48.4536 19.5644 46.8852L10.7317 36.0965L0.803482 24.1936C0.285669 23.5728 0 22.7739 0 21.9467V11.8619C0 10.9077 0.379815 9.99739 1.04667 9.35338L9.80913 0.890989C10.4026 0.317847 11.1777 0 11.982 0H32.018C32.8223 0 33.5974 0.317846 34.1909 0.890988L42.9533 9.35337C43.6202 9.99739 44 10.9077 44 11.8619V21.9467C44 22.7739 43.7143 23.5728 43.1965 24.1936L33.2683 36.0965Z"/>
<g fill="#306AFD">
<path d="M8.12354 14.532V20.5563C8.12354 21.0505 8.30373 21.5277 8.63035 21.8985L14.8928 29.0088L20.4642 35.4536C21.2741 36.3905 22.7268 36.3905 23.5368 35.4536L29.1081 29.0088L35.3706 21.8985C35.6972 21.5277 35.8774 21.0505 35.8774 20.5563V14.532C35.8774 13.962 35.6378 13.4182 35.2172 13.0335L29.6901 7.97841C29.3157 7.63604 28.8268 7.44617 28.3195 7.44617H15.6814C15.1741 7.44617 14.6852 7.63604 14.3108 7.97841L8.78374 13.0335C8.36311 13.4182 8.12354 13.962 8.12354 14.532Z"/>
<path d="M13.8626 33.5092L22 43.4487L30.1374 33.5092L39.9385 21.7588V12.0881L31.6272 4.06154H12.3728L4.06154 12.0881V21.7588L13.8626 33.5092ZM33.2683 36.0965L24.4356 46.8852C23.1516 48.4536 20.8484 48.4536 19.5644 46.8852L10.7317 36.0965L0.803482 24.1936C0.285669 23.5728 0 22.7739 0 21.9467V11.8619C0 10.9077 0.379815 9.99739 1.04667 9.35338L9.80913 0.890989C10.4026 0.317847 11.1777 0 11.982 0H32.018C32.8223 0 33.5974 0.317846 34.1909 0.890988L42.9533 9.35337C43.6202 9.99739 44 10.9077 44 11.8619V21.9467C44 22.7739 43.7143 23.5728 43.1965 24.1936L33.2683 36.0965Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,6 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M0.669171 5.0337e-06C0.299013 -0.00141788 -0.00141788 0.299013 5.0337e-06 0.669171V7.7807H1.33834V1.33834H12.8917V7.7807H14.2235V0.669171C14.2249 0.299013 13.9245 -0.00141778 13.5543 5.0337e-06H0.669171Z" />
<path d="M15.7647 7.78092C15.5905 7.78539 15.4249 7.85795 15.3035 7.98303C15.1821 8.10811 15.1145 8.27577 15.1152 8.45008V28.0002H16.447V11.2645L20.5145 19.4126C20.5679 19.5207 20.6496 19.6123 20.7509 19.6777C20.8522 19.743 20.9694 19.7796 21.0899 19.7837C21.2104 19.7877 21.3297 19.759 21.4352 19.7006C21.5407 19.6422 21.6283 19.5562 21.6888 19.452L26.6682 10.9103V28.0002H28V8.45008C27.9988 8.30448 27.9499 8.16328 27.8609 8.04806C27.7719 7.93283 27.6476 7.84992 27.507 7.81198C27.3665 7.77405 27.2173 7.78319 27.0824 7.83799C26.9476 7.8928 26.8343 7.99027 26.76 8.1155L21.1574 17.7134L16.3748 8.1483C16.3183 8.03564 16.231 7.94133 16.123 7.8763C16.015 7.81127 15.8908 7.7782 15.7647 7.78092Z" />
<path d="M6.22588 5.77979V7.55767L1.30554 9.61109C1.18522 9.66228 1.08261 9.74772 1.01048 9.85679C0.938359 9.96586 0.899902 10.0937 0.899902 10.2245C0.899902 10.3552 0.938359 10.4831 1.01048 10.5922C1.08261 10.7013 1.18522 10.7867 1.30554 10.8379L10.4902 14.6692L1.30554 18.5005C1.18522 18.5517 1.08261 18.6371 1.01048 18.7462C0.938359 18.8553 0.899902 18.9831 0.899902 19.1139C0.899902 19.2447 0.938359 19.3725 1.01048 19.4816C1.08261 19.5907 1.18522 19.6761 1.30554 19.7273L10.4967 23.5586L6.63919 25.1659C6.51794 25.2157 6.41407 25.3001 6.34056 25.4087C6.26705 25.5172 6.22716 25.645 6.22588 25.776V28H7.55765V26.2221L12.4845 24.1687C12.6049 24.1175 12.7075 24.0321 12.7796 23.923C12.8517 23.8139 12.8902 23.6861 12.8902 23.5553C12.8902 23.4246 12.8517 23.2967 12.7796 23.1876C12.7075 23.0786 12.6049 22.9931 12.4845 22.9419L3.29336 19.1172L12.4845 15.2859C12.6069 15.2357 12.7115 15.1502 12.7851 15.0404C12.8587 14.9306 12.898 14.8014 12.898 14.6692C12.898 14.537 12.8587 14.4078 12.7851 14.298C12.7115 14.1881 12.6069 14.1027 12.4845 14.0525L3.29336 10.2278L7.1509 8.62046C7.27195 8.56922 7.37514 8.48334 7.44751 8.37362C7.51989 8.26389 7.5582 8.13522 7.55765 8.00378V5.77979H6.22588Z" />
<path d="M6.89322 1.33838L4.22705 5.78199H9.55938L6.89322 1.33838Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,8 @@
<svg width="26" height="30" viewBox="0 0 26 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd">
<path d="M13.0014 8.64857C9.53838 8.64857 6.73109 11.4559 6.73109 14.9188C6.73109 18.3818 9.53838 21.1891 13.0014 21.1891C16.4643 21.1891 19.2716 18.3818 19.2716 14.9188C19.2716 11.4559 16.4643 8.64857 13.0014 8.64857ZM5.32568 14.9188C5.32568 10.6797 8.7622 7.24316 13.0014 7.24316C17.2405 7.24316 20.677 10.6797 20.677 14.9188C20.677 19.158 17.2405 22.5945 13.0014 22.5945C8.7622 22.5945 5.32568 19.158 5.32568 14.9188Z" />
<path d="M12.5692 26.7031L8.13672 30.0004V23.4058L12.5692 26.7031Z" />
<path d="M13.3801 26.7031L17.8125 30.0004V23.4058L13.3801 26.7031Z" />
<path d="M12.5692 3.2973L8.13672 6.59459V0L12.5692 3.2973Z" />
<path d="M13.3801 3.2973L17.8125 6.59459V0L13.3801 3.2973Z" />
<path d="M16.0016 25.5888L16.2719 26.9725C21.5818 25.5353 25.4881 20.6832 25.4881 14.9186C25.4881 9.15407 21.5818 4.3019 16.2719 2.86475V4.328C20.7955 5.72323 24.0827 9.93704 24.0827 14.9186C24.0827 19.9989 20.664 24.2806 16.0016 25.5888ZM9.73135 26.9725L10.353 25.6812C5.51204 24.4939 1.92054 20.1257 1.92054 14.9186C1.92054 9.71158 5.51204 5.34337 10.353 4.15607L9.38 2.96543C4.25001 4.51767 0.515137 9.28198 0.515137 14.9186C0.515137 20.6832 4.42143 25.5353 9.73135 26.9725Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -8,8 +8,7 @@ import { OpenAPI } from '@api'
import App from './App'
import '@styles/include/antd_theme.less'
import '@styles/index.css'
import '@styles/index.less'
// OpenAPI.BASE = 'http://localhost:3000'
// TODO: Удалить взятие из 'token' в следующем релизе, вставлено для совместимости

View File

@ -18,15 +18,13 @@ export const TelemetryInfo = memo(({ info, danger, ...other }) => (
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' }}
style={{ color: danger === true || danger?.includes(key) ? 'red' : '#ddd' }}
>{info?.[key] ?? '-'}</Item>
))}
</Descriptions>

View File

@ -1,4 +1,4 @@
import { Button, Input, Tag } from 'antd'
import { Button, Input } from 'antd'
import { UserSwitchOutlined } from '@ant-design/icons'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { BehaviorSubject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
@ -20,6 +20,7 @@ import { makeTextOnFilter, makeTextFilters, makeArrayOnFilter } from '@utils/fil
import { arrayOrDefault, withPermissions } from '@utils'
import RoleTag from './RoleTag'
import TagList from '@components/TagList'
const SEARCH_TIMEOUT = 400
@ -107,25 +108,20 @@ 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>
)) ?? '-'
const rolesRender = (item) => (
<TagList
items={item}
render={(elm) => (
<RoleView role={roles.find((role) => role.caption === elm)} />
)}
/>
)
setColumns([
makeTextColumn('Логин', 'login', null, null, null, {
formItemRules: [
{ required: true },
...createLoginRules,
// () => ({
// validator(_, value) {
// if (!value || users.findIndex((user) => user.login === value) < 0)
// return Promise.resolve()
// return Promise.reject(new Error('Логин уже занят!'))
// }
// })
// TODO: Для проверки уникальности логина необходимо исключить из выборки логин выбранного пользователя
],
}),
makeTextColumn('Фамилия', 'surname', filters.surname, null, null, {

View File

@ -1,13 +1,16 @@
import { memo, useMemo, useCallback } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { Map as PigeonMap, Overlay } from 'pigeon-maps'
import { Popover, Badge } from 'antd'
import { Popover, Button } from 'antd'
import { useDepositList } from '@asb/context'
import { PointerIcon } from '@components/icons'
import { useDepositList, useLayoutProps } from '@asb/context'
import { limitValue, withPermissions } from '@utils'
import '@styles/index.css'
import { ClusterMapIcon } from '@images/pages/deposit/ClusterMapIcon'
import { ReactComponent as MapPointer } from '@images/pages/deposit/MapPointer.svg'
import { ReactComponent as WellMapIcon } from '@images/pages/deposit/WellMapIcon.svg'
import '@styles/pages/deposit_map.less'
const zoomLimit = limitValue(5, 15)
@ -39,18 +42,19 @@ const calcViewParams = (clusters) => {
const Map = memo(() => {
const deposits = useDepositList()
const location = useLocation()
const { openWellTreeSelector } = useLayoutProps()
const makeDepositLinks = useCallback((clusters) => (
const makeClusterLinks = useCallback((wells) => (
<div>
{clusters.map(cluster => (
{wells.map(well => (
<Link
key={cluster.id}
key={well.id}
to={{
pathname: `/cluster/${cluster.id}`,
pathname: `/well/${well.id}`,
state: { from: location.pathname }
}}
>
<div>{cluster.caption}</div>
<div>{well.caption}</div>
</Link>
))}
</div>
@ -61,31 +65,39 @@ const Map = memo(() => {
return (
<div className={'deposit-page'}>
<PigeonMap {...viewParams}>
{deposits.map(deposit => {
const anchor = [deposit.latitude, deposit.longitude]
const links = makeDepositLinks(deposit.clusters)
{deposits.map(deposit => deposit.clusters.map(cluster => {
const anchor = [cluster.latitude, cluster.longitude]
const count = cluster.wells.length
const title = `${deposit.caption} / ${cluster.caption}`
const links = makeClusterLinks(cluster.wells)
return (
<Overlay width={32} anchor={anchor} key={anchor.join(' ')}>
<Overlay width={65} anchor={anchor} key={anchor.join(' ')}>
<Popover
content={links}
trigger={['click']}
title={(
<Link to={{ pathname: `/deposit/${deposit.id}` }}>
{deposit.caption}
<Link to={{ pathname: `/cluster/${cluster.id}` }}>
{title}
</Link>
)}
>
<div className={'pointer'}>
<Badge count={deposit.clusters.length}>
<PointerIcon state={'active'} width={48} height={59} />
</Badge>
<div className={'pointer'} title={title}>
{count <= 1 ? (
<WellMapIcon />
) : (
<ClusterMapIcon count={count} />
)}
</div>
</Popover>
</Overlay>
)
})}
}))}
</PigeonMap>
<button className={'dd-deposit-open-selector-btn'} onClick={openWellTreeSelector}>
<MapPointer />
<span>Выберите месторождение</span>
</button>
</div>
)
})

View File

@ -16,7 +16,7 @@ const breadcrumb = makeMenuBreadcrumbItemsRender(menuItems, /^\/deposit\/[^\/#?]
const Deposit = memo(() => {
const { '*': param } = useParams()
const setLayoutProps = useLayoutProps()
const { setLayoutProps } = useLayoutProps()
const deposits = useDepositList()
const root = useRootPath()

View File

@ -9,7 +9,6 @@ import { makeGroupColumn, makeNumericColumn, makeNumericRender, makeTextColumn,
import { OperationStatService, WellOperationService } from '@api'
import { arrayOrDefault, withPermissions } from '@utils'
import '@styles/index.css'
import '@styles/pages/statistics.less'
const { Text } = Typography

View File

@ -17,7 +17,6 @@ import { MeasureService } from '@api'
import { View } from './View'
import '@styles/index.css'
import '@styles/pages/measure.css'
const createEditingColumns = (cols, renderDelegate) =>

View File

@ -3,7 +3,6 @@ import { Empty, Form } from 'antd'
import { Grid, GridItem } from '@components/Grid'
import '@styles/index.css'
import '@styles/pages/measure.css'
export const View = memo(({ columns, item }) => !item || !columns?.length ? (

View File

@ -118,15 +118,16 @@ const DashboardNNB = memo(({ enableEditing = false }) => {
const navigate = useNavigate()
const selectedGroup = getTabname()
if (!selectedGroup && groups?.length > 0)
navigate(`${rootPath}/${groups[0].id}`)
const group = useMemo(() => ({
editable: true,
...groups.find(({ id }) => `${id}` === selectedGroup),
}), [groups, selectedGroup])
useEffect(() => {
if (!selectedGroup && groups?.length > 0)
navigate(`${rootPath}/${groups[0].id}`)
}, [selectedGroup, groups, rootPath, navigate])
useEffect(() => {
invokeWebApiWrapperAsync(
async () => {
@ -249,7 +250,7 @@ const DashboardNNB = memo(({ enableEditing = false }) => {
/>
))}
<WidgetSettingsWindow
visible={!!selectedSettings}
open={!!selectedSettings}
onCancel={() => setSelectedSettings(null)}
settings={selectedSettings}
onEdit={onEdit}

View File

@ -28,7 +28,7 @@ const categoryDictionary = {
// Конфигурация таблицы
export const makeMessageColumns = (idWell) => [
makeDateColumn('Дата', 'date', undefined, undefined, { width: '120px' }),
makeDateColumn('Дата', 'date', undefined, undefined, { width: 130 }),
makeNumericColumn('Глубина, м', 'wellDepth', (depth, item) => (
<Tooltip title={'Нажмите для перехода в архив'}>
<Link

View File

@ -13,15 +13,15 @@ import { useElementSize } from '@utils'
import { makeGetColor } from '@pages/Well/WellOperations/Tvd'
import '@styles/limiting_parameter_statistics.less'
import '@styles/pages/limiting_parameter_statistics.less'
const columns = [
makeColumn('Цвет', 'color', { width: 50, render: (d) => (
<div style={{ backgroundColor: d, padding: '5px 0' }} />
) }),
makeTextColumn('Уставка', 'nameFeedRegulator'),
makeNumericColumn('Проходка, м', 'depth'),
makeNumericColumn('Кол-во включений', 'numberInclusions', undefined, undefined, makeNumericRender(0)),
makeNumericColumn('Проходка, м', 'depth', undefined, undefined, 140),
makeNumericColumn('Кол-во включений', 'numberInclusions', makeNumericRender(0), undefined, 150),
]
export const LimitingParameterStatistics = memo(() => {
@ -59,16 +59,16 @@ export const LimitingParameterStatistics = memo(() => {
}
}
if (record.idFeedRegulator === selectedRegulator)
out.style = { background: '#FAFAFA', fontSize: '16px', fontWeight: '600' }
out.className = 'dd-selected-row'
return out
}, [selectedRegulator])
const onPieOver = useCallback(function (e, d) {
const onPieOver = useCallback(function (_, d) { // Стрелочная функция не подойдёт из-за использования `this`
setSelectedRegulator(d.data.idFeedRegulator)
d3.select(this).attr('transform', 'scale(1.05)')
}, [])
const onPieOut = useCallback(function (e, d) {
const onPieOut = useCallback(function () { // Стрелочная функция не подойдёт из-за использования `this`
setSelectedRegulator(null)
d3.select(this).attr('transform', 'scale(1)')
}, [])
@ -181,15 +181,15 @@ export const LimitingParameterStatistics = memo(() => {
<Button onClick={() => setIsOpen(true)}>Статистика использования уставок</Button>
<Modal
centered
width={1024}
width={950}
footer={false}
title={'Статистика использования уставок'}
onCancel={() => setIsOpen(false)}
open={isOpen}
>
<LoaderPortal show={isLoading}>
<LoaderPortal show={isLoading} className={'limit-parameter-stats-page'}>
<div className={'filter-groups'}>
<Input.Group compact style={{ flex: 1 }}>
<Input.Group compact style={{ flex: 2 }}>
<Input
addonBefore={(
<Radio
@ -203,7 +203,7 @@ export const LimitingParameterStatistics = memo(() => {
disabled={mode !== 'depth'}
prefix={'От'}
suffix={'м'}
style={{ width: 'calc(50% + 113px / 2)', textAlign: 'right' }}
style={{ width: 'calc(50% + 128px / 2)', textAlign: 'right' }}
onChange={(e) => onDepthChanged(e, 'from')}
value={depthFilter.from}
/>
@ -212,12 +212,12 @@ export const LimitingParameterStatistics = memo(() => {
disabled={mode !== 'depth'}
prefix={'До'}
suffix={'м'}
style={{ width: 'calc(50% - 113px / 2)', textAlign: 'right' }}
style={{ width: 'calc(50% - 128px / 2)', textAlign: 'right' }}
onChange={(e) => onDepthChanged(e, 'to')}
value={depthFilter.to}
/>
</Input.Group>
<Input.Group compact style={{ flex: 1 }}>
<Input.Group compact style={{ flex: 3 }}>
<Input style={{ width: 128 }} addonBefore={(
<Radio
checked={mode === 'time'}
@ -240,9 +240,9 @@ export const LimitingParameterStatistics = memo(() => {
{data ? (
<svg ref={setSvgRef} width={'100%'} height={'100%'}>
<g transform={`translate(${width / 2}, ${height / 2})`}>
<g className={'slices'} stroke={'#0005'} />
<g className={'labels'} fill={'black'} />
<g className={'lines'} fill={'none'} stroke={'black'} />
<g className={'slices'} />
<g className={'labels'} />
<g className={'lines'} />
</g>
</svg>
) : (

View File

@ -1,7 +1,6 @@
import { Button, Modal } from 'antd'
import { useState, useEffect, memo, useCallback, useMemo } from 'react'
import { useWell } from '@asb/context'
import { Table } from '@components/Table'
import { UserView } from '@components/views'
import LoaderPortal from '@components/LoaderPortal'
@ -12,7 +11,7 @@ import { SetpointsService } from '@api'
import SetpointSender from './SetpointSender'
import { SetpointViewer, getSetpointStatus } from './SetpointViewer'
export const Setpoints = memo(({ ...other }) => {
export const Setpoints = memo(({ well, ...other }) => {
const [isModalVisible, setIsModalVisible] = useState(false)
const [isSenderVisible, setIsSenderVisible] = useState(false)
const [isViewerVisible, setIsViewerVisible] = useState(false)
@ -21,8 +20,6 @@ export const Setpoints = memo(({ ...other }) => {
const [selected, setSelected] = useState(null)
const [setpointNames, setSetpointNames] = useState([])
const [well] = useWell()
useEffect(() => {
invokeWebApiWrapperAsync(
async () => {

View File

@ -1,17 +1,14 @@
import { memo, useCallback, useEffect, useState } from 'react'
import { useWell } from '@asb/context'
import { WirelineView } from '@components/views'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { TelemetryWirelineRunOutService } from '@api'
export const WirelineRunOut = memo(() => {
export const WirelineRunOut = memo(({ well }) => {
const [twro, setTwro] = useState({})
const [isLoading, setIsLoading] = useState(false)
const [well] = useWell()
const update = useCallback(() => invokeWebApiWrapperAsync(
async () => {
const twro = await TelemetryWirelineRunOutService.getData(well.id)

View File

@ -3,7 +3,7 @@ import { BehaviorSubject, buffer, throttleTime } from 'rxjs'
import { useSearchParams } from 'react-router-dom'
import { Alert, Button, Select } from 'antd'
import { useWell } from '@asb/context'
import { useTopRightBlock, useWell } from '@asb/context'
import LoaderPortal from '@components/LoaderPortal'
import { CopyUrlButton } from '@components/CopyUrl'
import { D3MonitoringCharts } from '@components/d3/monitoring'
@ -27,10 +27,8 @@ import WirelineRunOut from './WirelineRunOut'
import { Setpoints } from './Setpoints'
import { cursorRender } from './cursorRender'
import MomentStabPicEnabled from '@images/DempherOn.png'
import MomentStabPicDisabled from '@images/DempherOff.png'
import SpinPicEnabled from '@images/SpinEnabled.png'
import SpinPicDisabled from '@images/SpinDisabled.png'
import { ReactComponent as DempherIcon } from '@images/pages/well/telemetry/monitoring/DempherIcon.svg'
import { ReactComponent as SpinIcon } from '@images/pages/well/telemetry/monitoring/SpinIcon.svg'
import '@styles/pages/telemetry_view.less'
import '@styles/pages/message.less'
@ -76,6 +74,7 @@ const getRowDate = (row) => row && isRawDate(row.date) ? new Date(row.date) : nu
const TelemetryView = memo(() => {
const [well, updateWell] = useWell()
const setTopRightBlock = useTopRightBlock()
const [searchParams, setSearchParams] = useSearchParams()
const [dataSaub, setDataSaub] = useState([])
@ -91,6 +90,7 @@ const TelemetryView = memo(() => {
const [archiveMode, setArchiveMode] = useState(false)
const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well])
const handleDataSaub = useCallback((data, replace = false) => {
@ -251,6 +251,17 @@ const TelemetryView = memo(() => {
setEndDate(new Date(saubLast.date))
}, [archiveMode, saubLast])
useEffect(() => setTopRightBlock((well) => (
<div style={{ display: 'flex', gap: 5 }}>
<Setpoints well={well} />
<WirelineRunOut well={well} />
<Button onClick={() => setArchiveMode((prev) => !prev)} danger={archiveMode}>
{archiveMode ? 'Выйти из архива' : 'Войти в архив'}
</Button>
{archiveMode && <CopyUrlButton />}
</div>
)), [setTopRightBlock, archiveMode])
return (
<LoaderPortal show={showLoader} style={{ flexGrow: 1 }}>
<div className={'telemetry-view-page'}>
@ -265,13 +276,11 @@ const TelemetryView = memo(() => {
<Option value={2}>Завершено</Option>
</Select>
</div>
<Setpoints />
<LimitingParameterStatistics />
<WirelineRunOut />
<div className={'icons'}>
<img src={isTorqueStabEnabled(spinLast) ? MomentStabPicEnabled : MomentStabPicDisabled} alt={'TorqueMaster'} />
<img src={isSpinEnabled(spinLast) ? SpinPicEnabled : SpinPicDisabled} alt={'SpinMaster'} />
<h2 style={{ marginBottom: 0, fontWeight: 'bold', color: isMseEnabled(saubLast) ? 'green' : 'lightgrey' }}>MSE</h2>
<DempherIcon color={isTorqueStabEnabled(spinLast) ? '#24DE6E' : '#4F4F4F'} />
<SpinIcon color={isSpinEnabled(spinLast) ? '#24DE6E' : '#4F4F4F'} />
<spin style={{ fontSize: 24, fontWeight: 900, color: isMseEnabled(saubLast) ? '#24DE6E' : '#4F4F4F' }}>MSE</spin>
</div>
</div>
{archiveMode && (
@ -300,10 +309,6 @@ const TelemetryView = memo(() => {
<PeriodPicker value={chartInterval / 1000} onChange={(value) => setChartInterval(value * 1000)} />
</div>
<Button onClick={() => chartMethods?.setSettingsVisible(true)}>Настроить графики</Button>
<Button onClick={() => setArchiveMode((prev) => !prev)} danger={archiveMode}>
{archiveMode ? 'Выйти из архива' : 'Войти в архив'}
</Button>
{archiveMode && <CopyUrlButton />}
</div>
<D3MonitoringCharts
{...chartProps}

View File

@ -4,8 +4,6 @@ import { memo, useMemo } from 'react'
import { RootPathContext, useRootPath } from '@asb/context'
import { withPermissions } from '@utils'
import '@styles/index.css'
const Telemetry = memo(() => {
const root = useRootPath()
const rootPath = useMemo(() => `${root}/telemetry`, [root])

View File

@ -17,7 +17,6 @@ import StatExport from './StatExport'
import NetGraphExport from './NetGraphExport'
import AdditionalTables from './AdditionalTables'
import '@styles/index.css'
import '@styles/pages/tvd.less'
const operationsColors = [

View File

@ -10,8 +10,6 @@ import { WellService } from '@api'
import { WellNavigationMenu, menuItems } from './WellNavigationMenu'
import '@styles/index.css'
const Measure = lazy(() => import('./Measure'))
const Reports = lazy(() => import('./Reports'))
const WellCase = lazy(() => import('./WellCase'))
@ -50,7 +48,7 @@ const Well = memo(() => {
const root = useRootPath()
const rootPath = useMemo(() => `${root}/well/${idWell}`, [root, idWell])
const setLayoutProps = useLayoutProps()
const { setLayoutProps } = useLayoutProps()
const updateWell = useCallback((data) => invokeWebApiWrapperAsync(
async () => {

View File

@ -11,7 +11,7 @@ import { AuthService } from '@api'
import Logo from '@images/Logo'
import '@styles/index.css'
import '@styles/pages/login.less'
const Login = memo(() => {
const [showLoader, setShowLoader] = useState(false)
@ -36,10 +36,10 @@ const Login = memo(() => {
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Logo style={{ marginBottom: '10px' }} />
<LoaderPortal show={showLoader}>
<Card bordered title={'Система мониторинга'} className={'shadow'} style={{ width: 350 }}>
<Card bordered title={'Вход в ЕЦП'} className={'shadow'} style={{ width: 350 }}>
<Form onFinish={handleLogin}>
<Form.Item name={'login'} rules={loginRules}>
<Input placeholder={'Пользователь'} prefix={<UserOutlined />} />
<Input placeholder={'Логин'} prefix={<UserOutlined />} />
</Form.Item>
<Form.Item name={'password'} rules={passwordRules}>
<Input.Password placeholder={'Пароль'} prefix={<LockOutlined />} />

View File

@ -8,7 +8,6 @@ import {
EyeTwoTone
} from '@ant-design/icons'
import { AuthService } from '@api'
import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory'
import {
@ -21,9 +20,12 @@ import {
phoneRules
} from '@utils/validationRules'
import { withPermissions } from '@utils'
import { AuthService } from '@api'
import Logo from '@images/Logo'
import '@styles/pages/login.less'
const surnameRules = [...nameRules, { required: true, message: 'Пожалуйста, введите фамилию!' }]
const regEmailRules = [{ required: true, message: 'Пожалуйста, введите email!' }, ...emailRules]
const confirmPasswordRules = [
@ -38,7 +40,6 @@ const confirmPasswordRules = [
})
]
const logoIcon = <Logo width={130} />
const showPasswordIcon = visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)
const createInput = (name, placeholder, rules, isPassword, dependencies) => (
@ -66,29 +67,34 @@ export const Register = memo(() => {
), [navigate])
return (
<LoaderPortal show={showLoader} className={'loader-container login_page shadow'}>
<Card title={'Система мониторинга'} className={'shadow'} bordered={true} style={{ width: 350 }} extra={logoIcon}>
<Form onFinish={handleRegister}>
{createInput('login', 'Пользователь', [...loginRules, ...createLoginRules])}
{createInput('password', 'Пароль', [...passwordRules, ...createPasswordRules], true, null)}
{createInput('confirmPassword', 'Подтверждение пароля', confirmPasswordRules, true, ['password'])}
{createInput('name', 'Имя', nameRules)}
{createInput('surName', 'Фамилия', surnameRules)}
{createInput('patronymic', 'Отчество', nameRules)}
{createInput('email', 'Email', regEmailRules)}
{createInput('phone', 'Номер телефона', phoneRules)}
{createInput('position', 'Должность')}
<Form.Item>
<div className={'register-button'}>
<Button type={'primary'} htmlType={'submit'}>Зарегистрироваться</Button>
</div>
</Form.Item>
<div className={'text-align-center'}>
<Link to={`/login`}>Назад</Link>
</div>
</Form>
</Card>
</LoaderPortal>
<div className={'login_page shadow'}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Logo style={{ marginBottom: '10px' }} />
<LoaderPortal show={showLoader}>
<Card title={'Оставить заявку на регистрацию'} className={'shadow'} style={{ width: 350 }}>
<Form onFinish={handleRegister}>
{createInput('login', 'Логин', [...loginRules, ...createLoginRules])}
{createInput('password', 'Пароль', [...passwordRules, ...createPasswordRules], true, null)}
{createInput('confirmPassword', 'Подтверждение пароля', confirmPasswordRules, true, ['password'])}
{createInput('name', 'Имя', nameRules)}
{createInput('surName', 'Фамилия', surnameRules)}
{createInput('patronymic', 'Отчество', nameRules)}
{createInput('email', 'Email', regEmailRules)}
{createInput('phone', 'Номер телефона', phoneRules)}
{createInput('position', 'Должность')}
<Form.Item>
<div className={'register-button'}>
<Button type={'primary'} htmlType={'submit'}>Зарегистрироваться</Button>
</div>
</Form.Item>
<div className={'text-align-center'}>
<Link to={`/login`}>Назад</Link>
</div>
</Form>
</Card>
</LoaderPortal>
</div>
</div>
)
})

View File

@ -0,0 +1,4 @@
.download-link {
height: 32px;
padding: 4px 15px;
}

View File

@ -0,0 +1,3 @@
.dd-grid-item {
padding: 4px;
}

View File

@ -0,0 +1,6 @@
.dd-cluster-map-icon {
& .dd-cluster-map-icon-count {
font-size: 22px;
font-weight: 700;
}
}

View File

@ -2,6 +2,8 @@
@active-bg: #c32828;
@admin-bg: #900;
@admin-active-bg: #413f3d;
@bg-color: #232323;
@text-color: white - @bg-color;
.no-select {
-webkit-user-select: none;
@ -25,7 +27,7 @@
height: 100vh;
& .menu-sider {
& .dd-menu-sider {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
@ -86,7 +88,8 @@
& .page-content {
overflow-y: auto;
background: white;
background: @bg-color;
color: @text-color;
& .breadcrumb-block {
display: flex;
@ -115,11 +118,11 @@
background-color: @admin-bg;
}
.menu-sider {
.dd-menu-sider {
background-color: @admin-bg;
}
& .menu-sider .ant-menu-submenu-selected.ant-menu-submenu-inline .ant-menu-submenu-title {
& .dd-menu-sider .ant-menu-submenu-selected.ant-menu-submenu-inline .ant-menu-submenu-title {
color: white;
& .ant-menu-title-content > a {
color: white;
@ -128,10 +131,6 @@
}
}
.site-layout-background {
background: #fff;
}
@media only screen and (max-width: 1280px) {
.page-layout {
--sheet-padding: 10px;

View File

@ -1,20 +1,25 @@
/* original from https://loading.io/css/ */
@loader-bg: rgba(255, 255, 255, 0.1);
@loader-color: rgb(226, 29, 29);
.lds-ripple {
display: inline-block;
position: absolute;
width: 80px;
height: 80px;
}
.lds-ripple div {
position: absolute;
border: 4px solid rgb(226, 29, 29);
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
& div {
position: absolute;
border: 4px solid @loader-color;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
& div:nth-child(2) {
animation-delay: -0.5s;
}
}
@keyframes lds-ripple {
0% {
@ -33,12 +38,12 @@
}
}
.loader-container{
.loader-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.loader-content{
.loader-content {
width: 100%;
height: 100%;
display: flex;
@ -46,14 +51,14 @@
grid-column-start: 1;
grid-column-end: span 3;
grid-row-start: 1;
&.loader-content-fill {
height: 100%;
width: 100%;
}
}
.loader-content.loader-content-fill {
height: 100%;
width: 100%;
}
.loader-overlay{
.loader-overlay {
grid-column-start: 1;
grid-column-end: span 3;
grid-row-start: 1;
@ -66,13 +71,13 @@
border-radius: 40px;
}
.loader-fade{
.loader-fade {
grid-column-start: 1;
grid-column-end: span 3;
grid-row-start: 1;
background-color: rgba(255, 255, 255, 0.4);
background-color: @loader-bg;
align-self: stretch;
justify-self: stretch;
z-index: 1;
box-shadow: 0px 0px 6px 5px rgba(255, 254, 254, 0.4);
box-shadow: 0px 0px 6px 5px @loader-bg;
}

View File

@ -0,0 +1,13 @@
.first-column-title {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: space-between;
position: relative;
padding: 16px 0;
}
.text-align-r-container {
width: 100%;
text-align: right;
}

View File

@ -0,0 +1,9 @@
.dd-tag-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
& > .ant-tag {
margin-right: 0;
}
}

View File

@ -1,19 +0,0 @@
.header-tree-select {
width: 300px
}
.header-tree-select *{
color: #fff;
font-size: 1rem;
}
.header-tree-select{
width: 300px;
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.3);
}
.header-tree-select:hover{
border: 1px solid rgba(255, 255, 255, 0.8);
background-color: rgba(0, 0, 0, 0.5);
}

View File

@ -0,0 +1,26 @@
.well-tree-selector {
&.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
background-color: #C32828;
color: white;
& svg {
color: white !important;
}
}
}
.header-tree-select{
width: 300px;
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.3);
&:hover {
border: 1px solid rgba(255, 255, 255, 0.8);
background-color: rgba(0, 0, 0, 0.5);
}
& * {
color: #fff;
font-size: 1rem;
}
}

4
src/styles/fonts.less Normal file
View File

@ -0,0 +1,4 @@
@font-face {
font-family: 'Jost';
src: url('fonts/Jost-VariableFont_wght.ttf') format('truetype');
}

Binary file not shown.

View File

@ -1,11 +1,90 @@
@import '~antd/dist/antd.less';
/*
* ЭТО ФАЙЛ НАСТРОЙКИ ТЕМЫ И КОМПОНЕНТОВ ТЕМЫ.
* НЕ ПИШИТЕ ТУТ СТИЛИ ДЛЯ КАСТОМНЫХ КОМПОНЕНТОВ.
*/
// Переменные для темы тут:
// https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
@main: #222629;
@main-dark: #191C1E;
@main-light: #2E3338;
@text: #ddd;
@text-light: #fff;
@text-dark: #222;
@text-disabled: #999;
@primary-color: #C32828; // rgb(195, 40, 40)
@layout-header-background:#413F3D; // rgb(65, 63, 61)
@primary: #C32828;
@info: @primary;
@success: #06D6A0;
@error: #EF476F;
@warn: #fff200;
@layout-header-height: 48px;
@primary-color: @primary;
@processing-color: @info;
@info-color: @info;
@success-color: @success;
@error-color: @error;
@highlight-color: @error;
@warning-color: @warn;
@text-color: @text;
@text-color-dark: @text-dark;
@disabled-color: @text-disabled;
@text-color-secondary: @text-disabled;
@heading-color: @text-light;
@heading-color-dark: @text-dark;
@link-color: lighten(@primary, 10%);
@body-background: @main;
@component-background: @main-light;
@item-active-bg: @primary;
@item-hover-bg: @primary;
@menu-highlight-color: @text;
@table-row-hover-bg: darken(@primary, 10%);
@table-selected-row-bg: darken(@primary, 20%);
@descriptions-bg: @main-light;
@menu-bg: transparent;
@border-color-base: @main-dark;
@border-color-split: lighten(@main-light, 2%);
@background-color-light: @main-light;
@background-color-base: @main-light;
@checkbox-check-color: @main-light;
@layout-header-background: @main-dark;
@layout-body-background: @main;
// Alert
@alert-success-border-color: @success;
@alert-success-bg-color: darken(@success, 30%);
@alert-success-icon-color: @success;
@alert-info-border-color: @info;
@alert-info-bg-color: darken(@info, 30%);
@alert-info-icon-color: @info;
@alert-warning-border-color: @warn;
@alert-warning-bg-color: darken(@warn, 30%);
@alert-warning-icon-color: @warn;
@alert-error-border-color: @error;
@alert-error-bg-color: darken(@error, 30%);
@alert-error-icon-color: @error;
@select-item-selected-bg: darken(@primary, 10%); // Цвет выбранного элемента списка
@picker-basic-cell-active-with-range-color: darken(@primary, 10%); // Цвет выделения диапазона на селекторе дат
// Segmented
@segmented-label-color: @text;
@segmented-label-hover-color: @text;
@segmented-hover-bg: lighten(@main-light, 10%);
@segmented-bg: lighten(@main-light, 2%);
@segmented-selected-bg: darken(@primary, 10%);
.ant-menu-item:active, .ant-menu-submenu-title:active {
background-color: darken(@primary, 10%);
}
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background-color: @primary;
}

View File

@ -1,169 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.d-flex {
display: flex;
}
.flex-direction-column {
flex-direction: column;
}
.d-inline {
display: inline;
}
.d-none {
display: none;
}
.flex-1 {
flex: 1;
}
.w-15 {
width: 15%
}
.w-33 {
width: 33%
}
.w-50 {
width: 50%
}
.w-100 {
width: 100%
}
.m-0 {
margin: 0;
}
.mt-8px {
margin-top: 8px;
}
.mt-20px {
margin-top: 20px;
}
.mb-20px {
margin-bottom: 20px;
}
.ml-5px {
margin-left: 5px;
}
.ml-10px {
margin-left: 10px;
}
.ml-30px {
margin-left: 30px;
}
.h-100vh {
height: 100vh;
}
.p-10 {
padding: 10px;
}
.vertical-align-center {
vertical-align: center;
}
.text-align-center {
text-align: center;
}
.text-align-r-container {
width: 100%;
text-align: right;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.linkDocuments {
color: #000;
}
.linkDocuments:hover {
color: #c32828;
}
.container{
width: 100%;
}
.login-button {
width: 20%;
margin: auto;
}
.register-button {
width: 50%;
margin: auto;
}
.first-column-title {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: space-between;
position: relative;
padding: 16px 0;
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Edge, Opera and Firefox */
}
.download-link {
height: 32px;
padding: 4px 15px;
}
.ant-table-cell:has(.color-pale-green) {
background-color: #98fb98;
}
.ant-table-tbody > tr > td.ant-table-cell-row-hover:has( > div.color-pale-green) {
background: #98fb98;
}
.color-pale-green {
background-color: #98fb98;
}
.asb-grid-item {
padding: 4px;
}
.pointer {
cursor: pointer;
}
.deposit-page {
height: 100vh;
overflow: hidden;
}

51
src/styles/index.less Normal file
View File

@ -0,0 +1,51 @@
@import './include/antd_theme.less';
@import './fonts';
@import './mixins';
body {
margin: 0;
font-family: 'Jost', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #232323;
}
.d-none { display: none }
.d-flex { display: flex }
.d-inline { display: inline }
.flex-direction-column { flex-direction: column }
.flex-1 { flex: 1 }
.w-15 { width: 15% }
.w-33 { width: 33% }
.w-50 { width: 50% }
.w-100 { width: 100% }
.h-100vh { height: 100vh }
.m-0 { margin: 0 }
.mt-8px { margin-top: 8px }
.mt-20px { margin-top: 20px }
.mb-20px { margin-bottom: 20px }
.ml-5px { margin-left: 5px }
.ml-10px { margin-left: 10px }
.ml-30px { margin-left: 30px }
.p-10 { padding: 10px }
.text-align-center {
text-align: center;
}
.container {
width: 100%;
}
.pointer { cursor: pointer }
.no-select {
.no-select();
}

View File

@ -1,29 +0,0 @@
.filter-groups {
display: flex;
gap: 10px;
& .filter-label {
display: flex;
align-items: center;
}
& .date-filter {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
}
.modal-label {
width: 100%;
margin: 20px 0;
font-size: 1rem;
text-align: center;
}
.lps-pie-chart {
min-height: 30vh;
max-height: 50vh;
}

13
src/styles/mixins.less Normal file
View File

@ -0,0 +1,13 @@
.no-select () {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Edge, Opera and Firefox */
}
.no-events () {
pointer-events: none;
}

View File

@ -1,4 +1,4 @@
@import '../components/loader.css';
@import '../components/loader.less';
#root, .app{
min-height:100%;
@ -25,17 +25,6 @@ html {
margin-left: 30px;
}
.login_page {
position: absolute;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 24px;
background-color: #9d9d9d;
}
.shadow{
box-shadow: 1px 1px 4px #00000033;
}

View File

@ -0,0 +1,50 @@
.deposit-page {
@bg-color: #232323;
height: 100vh;
overflow: hidden;
& > div[dir="ltr"] {
background: @bg-color !important;
}
& .pigeon-tiles-box > .pigeon-tiles > * {
filter: brightness(0.6) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7) grayscale(1);
}
& .pigeon-attribution {
background: fade(@bg-color, 70%) !important;
color: white !important;
}
& .dd-deposit-open-selector-btn {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
padding: 10px 25px;
color: white;
font-weight: 700;
font-size: 16px;
font-style: normal;
position: absolute;
gap: 10px;
top: 35px;
right: 60px;
border: 1px solid #4A4A4A;
background: @bg-color;
border-radius: 6px;
box-sizing: border-box;
transition: all .1s ease-in-out;
&:hover {
color: @bg-color;
background: white;
}
&:active {
background: rgba(255,255,255,.6);
color: @bg-color;
}
}
}

View File

@ -0,0 +1,54 @@
@import '../include/antd_theme';
@import '../mixins';
.limit-parameter-stats-page {
& .filter-groups {
display: flex;
gap: 10px;
& .filter-label {
display: flex;
align-items: center;
}
& .date-filter {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
}
& .modal-label {
width: 100%;
margin: 20px 0;
font-size: 1rem;
text-align: center;
}
.dd-selected-row {
background: @primary;
font-size: 16px;
font-weight: 600;
}
& .lps-pie-chart {
min-height: 30vh;
max-height: 50vh;
& .slices {
stroke: #0005;
}
& .labels {
fill: white;
}
& .lines {
fill: none;
stroke: white;
.no-events();
}
}
}

View File

@ -0,0 +1,20 @@
.login-button {
width: 20%;
margin: auto;
}
.register-button {
width: 50%;
margin: auto;
}
.login_page {
position: absolute;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 24px;
background-color: #232323;
}

View File

@ -34,16 +34,11 @@
background: gold;
}
.event_message_3 {
color: #c0c0c0;
background: #505060;
}
td.ant-table-column-sort {
color: black;
background-color: #fafafa;
}
.ant-table-tbody > tr > td.ant-table-cell-row-hover {
color: black;
color: white;
}

View File

@ -22,10 +22,8 @@
& .icons {
display: flex;
& > * {
margin-left: 15px;
}
align-items: center;
gap: 15px;
}
}

View File

@ -7,8 +7,8 @@
align-items: stretch;
margin: 10px;
padding: 5px 10px;
border-radius: 5px;
background: #00000009;
border-radius: 6px;
background: #2F2F2F;
height: @size;
min-width: @size * 1.75;

View File

@ -15,7 +15,7 @@ export const createLoginRules: Readonly<Rule[]> = [_min1, {
export const loginRules: Readonly<Rule[]> = [...createLoginRules, {
required: true,
message: 'Пожалуйста, введите логин',
message: 'Пожалуйста, введите логин!',
}]
export const nameRules: Readonly<Rule[]> = [_min1, {