forked from ddrilling/asb_cloud_front
Документирована часть комментариев и функций
This commit is contained in:
parent
d235b01c80
commit
7af33d702d
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"день"
|
"день",
|
||||||
|
"Saub"
|
||||||
],
|
],
|
||||||
"liveServer.settings.port": 5501
|
"liveServer.settings.port": 5501
|
||||||
}
|
}
|
@ -5,6 +5,11 @@ import { DatePickerWrapper, getObjectByDeepKey } from '..'
|
|||||||
import { DatePickerWrapperProps } from '../DatePickerWrapper'
|
import { DatePickerWrapperProps } from '../DatePickerWrapper'
|
||||||
import { formatDate, isRawDate } from '@utils'
|
import { formatDate, isRawDate } from '@utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фабрика методов сортировки столбцов для данных типа **Дата**
|
||||||
|
* @param key Ключ столбца
|
||||||
|
* @returns Метод сортировки
|
||||||
|
*/
|
||||||
export const makeDateSorter = <T extends unknown>(key: Key): SorterMethod<T> => (a, b) => {
|
export const makeDateSorter = <T extends unknown>(key: Key): SorterMethod<T> => (a, b) => {
|
||||||
const vA = a ? getObjectByDeepKey(key, a) : null
|
const vA = a ? getObjectByDeepKey(key, a) : null
|
||||||
const vB = b ? getObjectByDeepKey(key, b) : null
|
const vB = b ? getObjectByDeepKey(key, b) : null
|
||||||
@ -16,6 +21,17 @@ export const makeDateSorter = <T extends unknown>(key: Key): SorterMethod<T> =>
|
|||||||
return (new Date(vA)).getTime() - (new Date(vB)).getTime()
|
return (new Date(vA)).getTime() - (new Date(vB)).getTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фабрика объектов-столбцов для компонента `Table` для работы с данными типа **Дата**
|
||||||
|
*
|
||||||
|
* @param title Название столбца
|
||||||
|
* @param key Ключ столбца
|
||||||
|
* @param utc Конвертировать ли дату в UTC
|
||||||
|
* @param format Формат отображения даты
|
||||||
|
* @param other Дополнительные опции столбца
|
||||||
|
* @param pickerOther Опции компонента селектора даты
|
||||||
|
* @returns Объект-столбец для работы с данными типа **Дата**
|
||||||
|
*/
|
||||||
export const makeDateColumn = <T extends unknown>(
|
export const makeDateColumn = <T extends unknown>(
|
||||||
title: ReactNode,
|
title: ReactNode,
|
||||||
key: string,
|
key: string,
|
||||||
|
@ -6,8 +6,11 @@ import moment, { Moment } from 'moment'
|
|||||||
import { defaultFormat } from '@utils'
|
import { defaultFormat } from '@utils'
|
||||||
|
|
||||||
export type DatePickerWrapperProps = PickerDateProps<Moment> & {
|
export type DatePickerWrapperProps = PickerDateProps<Moment> & {
|
||||||
|
/** Значение селектора */
|
||||||
value?: Moment,
|
value?: Moment,
|
||||||
|
/** Метод вызывается при изменений даты */
|
||||||
onChange?: (date: Moment | null) => any
|
onChange?: (date: Moment | null) => any
|
||||||
|
/** Конвертировать ли значение в UTC */
|
||||||
isUTC?: boolean
|
isUTC?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,31 +9,41 @@ import { defaultFormat } from '@utils'
|
|||||||
const { RangePicker } = DatePicker
|
const { RangePicker } = DatePicker
|
||||||
|
|
||||||
export type DateRangeWrapperProps = RangePickerSharedProps<Moment> & {
|
export type DateRangeWrapperProps = RangePickerSharedProps<Moment> & {
|
||||||
value?: RangeValue<Moment>,
|
/** Значение селектора в виде массива из 2 элементов (от, до) */
|
||||||
|
value?: RangeValue<Moment>
|
||||||
|
/** Конвертировать ли значения в UTC */
|
||||||
isUTC?: boolean
|
isUTC?: boolean
|
||||||
|
/** Разрешить сброс значения селектора */
|
||||||
allowClear?: boolean
|
allowClear?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подготавливает значения к передаче в селектор
|
||||||
|
*
|
||||||
|
* @param value Массиз из 2 дат
|
||||||
|
* @param isUTC Конвертировать ли значения в UTC
|
||||||
|
* @returns Подготовленные даты
|
||||||
|
*/
|
||||||
const normalizeDates = (value?: RangeValue<Moment>, isUTC?: boolean): RangeValue<Moment> => {
|
const normalizeDates = (value?: RangeValue<Moment>, isUTC?: boolean): RangeValue<Moment> => {
|
||||||
if (!value) return [null, null]
|
if (!value) return [null, null]
|
||||||
return [
|
return [
|
||||||
value[0] ? (isUTC ? moment.utc(value[0]).local() : moment(value[0])) : null,
|
value[0] ? (isUTC ? moment.utc(value[0]).local() : moment(value[0])) : null,
|
||||||
value[1] ? (isUTC ? moment.utc(value[1]).local() : moment(value[1])) : null,
|
value[1] ? (isUTC ? moment.utc(value[1]).local() : moment(value[1])) : null,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DateRangeWrapper = memo<DateRangeWrapperProps>(({ value, isUTC, allowClear = false, ...other }) => (
|
export const DateRangeWrapper = memo<DateRangeWrapperProps>(({ value, isUTC, allowClear, ...other }) => (
|
||||||
<RangePicker
|
<RangePicker
|
||||||
showTime
|
showTime
|
||||||
allowClear={allowClear}
|
allowClear={allowClear}
|
||||||
format={defaultFormat}
|
format={defaultFormat}
|
||||||
defaultValue={[
|
defaultValue={[
|
||||||
moment().subtract(1, 'days').startOf('day'),
|
moment().subtract(1, 'days').startOf('day'),
|
||||||
moment().startOf('day'),
|
moment().startOf('day'),
|
||||||
]}
|
]}
|
||||||
value={normalizeDates(value)}
|
value={normalizeDates(value, isUTC)}
|
||||||
{...other}
|
{...other}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|
||||||
export default DateRangeWrapper
|
export default DateRangeWrapper
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Key, memo, useCallback, useEffect, useState } from 'react'
|
import { Key, memo, useCallback, useEffect, useState } from 'react'
|
||||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table'
|
import { ColumnGroupType, ColumnType } from 'antd/lib/table'
|
||||||
import { Table as RawTable, TableProps } from 'antd'
|
import { Table as RawTable, TableProps as RawTableProps } from 'antd'
|
||||||
|
|
||||||
import { RenderMethod } from './Columns'
|
import { RenderMethod } from './Columns'
|
||||||
import { tryAddKeys } from './EditableTable'
|
import { tryAddKeys } from './EditableTable'
|
||||||
@ -14,16 +14,28 @@ export type BaseTableColumn<T> = ColumnGroupType<T> | ColumnType<T>
|
|||||||
export type TableColumn<T> = OmitExtends<BaseTableColumn<T>, TableColumnSettings>
|
export type TableColumn<T> = OmitExtends<BaseTableColumn<T>, TableColumnSettings>
|
||||||
export type TableColumns<T> = TableColumn<T>[]
|
export type TableColumns<T> = TableColumn<T>[]
|
||||||
|
|
||||||
export type TableContainer<T> = TableProps<T> & {
|
export type TableProps<T> = RawTableProps<T> & {
|
||||||
|
/** Массив колонок таблицы с настройками (описаны в `TableColumnSettings`) */
|
||||||
columns: TableColumn<T>[]
|
columns: TableColumn<T>[]
|
||||||
|
/** Название таблицы для сохранения настроек */
|
||||||
tableName?: string
|
tableName?: string
|
||||||
|
/** Отображать ли кнопку настроек */
|
||||||
showSettingsChanger?: boolean
|
showSettingsChanger?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataSet<T, D = any> {
|
export interface DataSet<T, D = any> {
|
||||||
[k: Key]: DataSet<T> | T | D
|
[k: Key]: DataSet<T, D> | T | D
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить значение из объекта по составному ключу
|
||||||
|
*
|
||||||
|
* Составной ключ имеет вид: `<поле 1>[.<поле 2>...]`
|
||||||
|
*
|
||||||
|
* @param key Составной ключ
|
||||||
|
* @param data Объект из которого будет полученно значение
|
||||||
|
* @returns Значение, найденное по ключу, либо `undefined`
|
||||||
|
*/
|
||||||
export const getObjectByDeepKey = <T,>(key: Key | undefined, data: DataSet<T>): T | undefined => {
|
export const getObjectByDeepKey = <T,>(key: Key | undefined, data: DataSet<T>): T | undefined => {
|
||||||
if (!key) return undefined
|
if (!key) return undefined
|
||||||
const parts = String(key).split('.')
|
const parts = String(key).split('.')
|
||||||
@ -36,36 +48,44 @@ export const getObjectByDeepKey = <T,>(key: Key | undefined, data: DataSet<T>):
|
|||||||
return out as T
|
return out as T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фабрика обёрток render-функций ячеек с поддержкой составных ключей
|
||||||
|
* @param key Составной ключ
|
||||||
|
* @param render Стандартная render-функция
|
||||||
|
* @returns Обёрнутая render-функция
|
||||||
|
*/
|
||||||
export const makeColumnRenderWrapper = <T extends DataSet<any>>(key: Key | undefined, render: RenderMethod<T, T> | undefined): RenderMethod<T, T> =>
|
export const makeColumnRenderWrapper = <T extends DataSet<any>>(key: Key | undefined, render: RenderMethod<T, T> | undefined): RenderMethod<T, T> =>
|
||||||
(_: any, dataset: T, index: number) => {
|
(_: any, dataset: T, index: number) => {
|
||||||
const renderFunc: RenderMethod<T, T> = typeof render === 'function' ? render : (record) => String(record)
|
const renderFunc: RenderMethod<T, T> = typeof render === 'function' ? render : (record) => String(record)
|
||||||
return renderFunc(getObjectByDeepKey<T>(key, dataset), dataset, index)
|
return renderFunc(getObjectByDeepKey<T>(key, dataset), dataset, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
const applyColumnWrappers = <T extends DataSet<any>>(columns: BaseTableColumn<T>[]): BaseTableColumn<T>[] => {
|
* Применяет необходимые обёртки ко всем столбцам таблицы
|
||||||
return columns.map((column) => {
|
* @param columns Исходные столбцы
|
||||||
if ('children' in column) {
|
* @returns Обёрнутые столбцы
|
||||||
return {
|
*/
|
||||||
...column,
|
const applyColumnWrappers = <T extends DataSet<any>>(columns: TableColumns<T>): TableColumns<T> => columns.map((column) => {
|
||||||
children: applyColumnWrappers(column.children),
|
if ('children' in column) {
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...column,
|
...column,
|
||||||
render: makeColumnRenderWrapper<T>(column.key, column.render),
|
children: applyColumnWrappers(column.children),
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
return {
|
||||||
|
...column,
|
||||||
|
render: makeColumnRenderWrapper<T>(column.key, column.render),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function _Table<T extends DataSet<any>>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer<T>) {
|
function _Table<T extends DataSet<any>>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableProps<T>) {
|
||||||
const [newColumns, setNewColumns] = useState<TableColumn<T>[]>([])
|
const [newColumns, setNewColumns] = useState<TableColumn<T>[]>([])
|
||||||
const [settings, setSettings] = useState<TableSettings>({})
|
const [settings, setSettings] = useState<TableSettings>({})
|
||||||
|
|
||||||
const onSettingsChanged = useCallback((settings?: TableSettings | null) => {
|
const onSettingsChanged = useCallback((settings?: TableSettings | null) => {
|
||||||
if (tableName)
|
if (tableName)
|
||||||
setTableSettings(tableName, settings)
|
setTableSettings(tableName, settings)
|
||||||
setSettings(settings ?? {})
|
setSettings(settings || {})
|
||||||
}, [tableName])
|
}, [tableName])
|
||||||
|
|
||||||
useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName])
|
useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName])
|
||||||
@ -92,6 +112,13 @@ function _Table<T extends DataSet<any>>({ columns, dataSource, tableName, showSe
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обёртка над компонентом таблицы AntD
|
||||||
|
*
|
||||||
|
* Особенности:
|
||||||
|
* * Поддержка составных ключей столбцов
|
||||||
|
* * Работа с настройками столбцов таблицы
|
||||||
|
*/
|
||||||
export const Table = memo(_Table) as typeof _Table
|
export const Table = memo(_Table) as typeof _Table
|
||||||
|
|
||||||
export default Table
|
export default Table
|
||||||
|
@ -6,8 +6,11 @@ import { defaultTimeFormat, momentToTime, timeToMoment } from '@utils'
|
|||||||
import { TimeDto } from '@api'
|
import { TimeDto } from '@api'
|
||||||
|
|
||||||
export type TimePickerWrapperProps = Omit<Omit<TimePickerProps, 'value'>, 'onChange'> & {
|
export type TimePickerWrapperProps = Omit<Omit<TimePickerProps, 'value'>, 'onChange'> & {
|
||||||
|
/** Текущее значение */
|
||||||
value?: TimeDto,
|
value?: TimeDto,
|
||||||
|
/** Метод вызывается при изменений времени */
|
||||||
onChange?: (date: TimeDto | null) => any
|
onChange?: (date: TimeDto | null) => any
|
||||||
|
/** Конвертировать ли время в UTC */
|
||||||
isUTC?: boolean
|
isUTC?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,13 @@ export type PaginationContainer<T> = {
|
|||||||
items?: T[] | null
|
items?: T[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерирует объект пагинации для компонента `Table` из данных от сервисов
|
||||||
|
*
|
||||||
|
* @param сontainer данные от сервиса
|
||||||
|
* @param other Дополнительные поля (передаются в объект напрямую в приоритете)
|
||||||
|
* @returns Объект пагинации
|
||||||
|
*/
|
||||||
export const makePaginationObject = <T, M extends object>(сontainer: PaginationContainer<T>, other: M) => ({
|
export const makePaginationObject = <T, M extends object>(сontainer: PaginationContainer<T>, other: M) => ({
|
||||||
...other,
|
...other,
|
||||||
pageSize: сontainer.take,
|
pageSize: сontainer.take,
|
||||||
|
@ -9,6 +9,7 @@ export type CompanyViewProps = {
|
|||||||
company?: CompanyDto
|
company?: CompanyDto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Компонент для отображения информации о компании */
|
||||||
export const CompanyView = memo<CompanyViewProps>(({ company }) => company ? (
|
export const CompanyView = memo<CompanyViewProps>(({ company }) => company ? (
|
||||||
<Tooltip title={
|
<Tooltip title={
|
||||||
<Grid style={{ columnGap: '8px' }}>
|
<Grid style={{ columnGap: '8px' }}>
|
||||||
|
@ -8,6 +8,7 @@ export type PermissionViewProps = {
|
|||||||
info?: PermissionDto
|
info?: PermissionDto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Компонент для отображения информации о разрешении */
|
||||||
export const PermissionView = memo<PermissionViewProps>(({ info }) => info ? (
|
export const PermissionView = memo<PermissionViewProps>(({ info }) => info ? (
|
||||||
<Tooltip overlayInnerStyle={{ width: '400px' }} title={
|
<Tooltip overlayInnerStyle={{ width: '400px' }} title={
|
||||||
<Grid>
|
<Grid>
|
||||||
|
@ -9,6 +9,7 @@ export type RoleViewProps = {
|
|||||||
role?: UserRoleDto
|
role?: UserRoleDto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Компонент для отображения информации о роли */
|
||||||
export const RoleView = memo<RoleViewProps>(({ role }) => {
|
export const RoleView = memo<RoleViewProps>(({ role }) => {
|
||||||
if (!role) return ( <Tooltip title={'нет данных'}>-</Tooltip> )
|
if (!role) return ( <Tooltip title={'нет данных'}>-</Tooltip> )
|
||||||
|
|
||||||
|
@ -19,6 +19,12 @@ export const lables: Record<string, string> = {
|
|||||||
spinPlcVersion: 'Версия Спин Мастер',
|
spinPlcVersion: 'Версия Спин Мастер',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Строит название для телеметрии
|
||||||
|
*
|
||||||
|
* @param telemetry Объект телеметрии
|
||||||
|
* @returns Название
|
||||||
|
*/
|
||||||
export const getTelemetryLabel = (telemetry?: TelemetryDto) =>
|
export const getTelemetryLabel = (telemetry?: TelemetryDto) =>
|
||||||
`${telemetry?.id ?? '-'} / ${telemetry?.info?.deposit ?? '-'} / ${telemetry?.info?.cluster ?? '-'} / ${telemetry?.info?.well ?? '-'}`
|
`${telemetry?.id ?? '-'} / ${telemetry?.info?.deposit ?? '-'} / ${telemetry?.info?.cluster ?? '-'} / ${telemetry?.info?.well ?? '-'}`
|
||||||
|
|
||||||
@ -26,6 +32,7 @@ export type TelemetryViewProps = {
|
|||||||
telemetry?: TelemetryDto
|
telemetry?: TelemetryDto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Компонент для отображения информации о телеметрии */
|
||||||
export const TelemetryView = memo<TelemetryViewProps>(({ telemetry }) => telemetry?.info ? (
|
export const TelemetryView = memo<TelemetryViewProps>(({ telemetry }) => telemetry?.info ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
overlayInnerStyle={{ width: '400px' }}
|
overlayInnerStyle={{ width: '400px' }}
|
||||||
|
@ -10,6 +10,7 @@ export type UserViewProps = HTMLProps<HTMLSpanElement> & {
|
|||||||
user?: UserDto
|
user?: UserDto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Компонент для отображения информации о пользователе */
|
||||||
export const UserView = memo<UserViewProps>(({ user, ...other }) =>
|
export const UserView = memo<UserViewProps>(({ user, ...other }) =>
|
||||||
user ? (
|
user ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -19,8 +19,14 @@ export type WellViewProps = TooltipProps & {
|
|||||||
labelProps?: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
|
labelProps?: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить название скважины
|
||||||
|
* @param well Объект с данными скважины
|
||||||
|
* @returns Название скважины
|
||||||
|
*/
|
||||||
export const getWellTitle = (well: WellDto) => `${well.deposit || '-'} / ${well.cluster || '-'} / ${well.caption || '-'}`
|
export const getWellTitle = (well: WellDto) => `${well.deposit || '-'} / ${well.cluster || '-'} / ${well.caption || '-'}`
|
||||||
|
|
||||||
|
/** Компонент для отображения информации о скважине */
|
||||||
export const WellView = memo<WellViewProps>(({ well, iconProps, labelProps, ...other }) => well ? (
|
export const WellView = memo<WellViewProps>(({ well, iconProps, labelProps, ...other }) => well ? (
|
||||||
<Tooltip {...other} title={(
|
<Tooltip {...other} title={(
|
||||||
<Grid style={{ columnGap: '8px' }}>
|
<Grid style={{ columnGap: '8px' }}>
|
||||||
|
@ -21,6 +21,7 @@ export type WirelineViewProps = TooltipProps & {
|
|||||||
buttonProps?: ButtonProps
|
buttonProps?: ButtonProps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Компонент для отображения информации о талевом канате */
|
||||||
export const WirelineView = memo<WirelineViewProps>(({ wireline, buttonProps, ...other }) => (
|
export const WirelineView = memo<WirelineViewProps>(({ wireline, buttonProps, ...other }) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
{...other}
|
{...other}
|
||||||
|
@ -7,7 +7,7 @@ import LoaderPortal from '@components/LoaderPortal'
|
|||||||
import { DateRangeWrapper } from '@components/Table'
|
import { DateRangeWrapper } from '@components/Table'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { unique } from '@utils/filters'
|
import { unique } from '@utils/filters'
|
||||||
import { getPermissions, arrayOrDefault, range, withPermissions, pretify } from '@utils'
|
import { getPermissions, arrayOrDefault, range, withPermissions, prettify } from '@utils'
|
||||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||||
|
|
||||||
import DrillerList from './DrillerList'
|
import DrillerList from './DrillerList'
|
||||||
@ -67,7 +67,7 @@ const Operations = memo(() => {
|
|||||||
const maxTarget = Math.max(...data.operations?.map((op) => op.operationValue?.targetValue || 0))
|
const maxTarget = Math.max(...data.operations?.map((op) => op.operationValue?.targetValue || 0))
|
||||||
const uniqueOps = data.operations?.map((op) => op.value || 0).filter(unique)
|
const uniqueOps = data.operations?.map((op) => op.value || 0).filter(unique)
|
||||||
const value = uniqueOps.reduce((out, op) => out + op, 0) / uniqueOps.length * 3 / 2
|
const value = uniqueOps.reduce((out, op) => out + op, 0) / uniqueOps.length * 3 / 2
|
||||||
setYDomain(pretify(Math.max(maxTarget, value)))
|
setYDomain(prettify(Math.max(maxTarget, value)))
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -8,7 +8,7 @@ import { useTopRightBlock, useWell } from '@asb/context'
|
|||||||
import { D3Chart } from '@components/d3'
|
import { D3Chart } from '@components/d3'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { formatDate, fractionalSum, withPermissions, getOperations, pretify } from '@utils'
|
import { formatDate, fractionalSum, withPermissions, getOperations, prettify } from '@utils'
|
||||||
|
|
||||||
import TLPie from './TLPie'
|
import TLPie from './TLPie'
|
||||||
import TLChart from './TLChart'
|
import TLChart from './TLChart'
|
||||||
@ -180,7 +180,7 @@ const Tvd = memo(({ well: givenWell, title, ...other }) => {
|
|||||||
.map(([_, ops]) => Math.max(...ops.map((op) => op.depth).filter(Boolean)))
|
.map(([_, ops]) => Math.max(...ops.map((op) => op.depth).filter(Boolean)))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
)
|
)
|
||||||
const minValue = pretify(maxValue)
|
const minValue = prettify(maxValue)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: {
|
date: {
|
||||||
|
@ -1,12 +1,30 @@
|
|||||||
|
import { getObjectByDeepKey } from "@asb/components/Table"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фабрика методов фильтрации строк таблицы по столбцу с текстом
|
||||||
|
* @param key Составной ключ столбца
|
||||||
|
* @returns Метод фильтрации
|
||||||
|
*/
|
||||||
export const makeTextOnFilter = (key: string) =>
|
export const makeTextOnFilter = (key: string) =>
|
||||||
(value: string, record?: Record<string, unknown>) => String(record?.[key]).startsWith(value)
|
(value: string, record?: Record<string, unknown>) => record && String(getObjectByDeepKey(key, record)).startsWith(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фабрика методов фильтрации строк таблицы по столбцу с массивами
|
||||||
|
* @param key Составной ключ столбца
|
||||||
|
* @returns Метод фильтрации
|
||||||
|
*/
|
||||||
export const makeArrayOnFilter = (key: string) =>
|
export const makeArrayOnFilter = (key: string) =>
|
||||||
(value: string, record?: Record<string, string[]>) => (!value && (record?.[key]?.length ?? 0) <= 0) || record?.[key]?.includes(value)
|
(value: string, record?: Record<string, string[]>) => record && (
|
||||||
|
(!value && (getObjectByDeepKey<any[]>(key, record)?.length ?? 0) <= 0) ||
|
||||||
export const makeObjectOnFilter = (field: string, key: string) =>
|
getObjectByDeepKey<any[]>(key, record)?.includes(value)
|
||||||
(value: string, record?: Record<string, Record<string, unknown>>) => String(record?.[field]?.[key]).startsWith(value)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создаёт список значений для фильтрации текстовых столбцов
|
||||||
|
* @param array Массив значений
|
||||||
|
* @param keys Массив ключей
|
||||||
|
* @returns Список значений
|
||||||
|
*/
|
||||||
export const makeTextFilters = (array: Record<string, unknown>[], keys: string[]) => {
|
export const makeTextFilters = (array: Record<string, unknown>[], keys: string[]) => {
|
||||||
const filters: string[][] = Array(keys.length)
|
const filters: string[][] = Array(keys.length)
|
||||||
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
export * from './columnFilters'
|
export * from './columnFilters'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет значение на уникальность в массиве
|
||||||
|
*
|
||||||
|
* @param value Проверяемое значение
|
||||||
|
* @param index Индекс проверяемого значения в массиве
|
||||||
|
* @param self Массив, содержащий значение
|
||||||
|
* @returns Является ли значение уникальным или первым
|
||||||
|
*/
|
||||||
export const unique = <T,>(value: T, index: number, self: T[]) => self.indexOf(value) === index
|
export const unique = <T,>(value: T, index: number, self: T[]) => self.indexOf(value) === index
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Возвращает
|
* Гарантированно возвращает массив нужного типа
|
||||||
*
|
*
|
||||||
* @param arr Входящие данные
|
* @param arr Входящие данные
|
||||||
* @param def Значение по-умолчанию
|
* @param def Значение по-умолчанию
|
||||||
*
|
*
|
||||||
* @returns Если `arr` - массив будет возвращено оно, иначе `def`
|
* @returns Если входящие данные - массив будут возвращены они, иначе значение из `def`
|
||||||
*/
|
*/
|
||||||
export const arrayOrDefault = <T,>(arr: unknown, def: T[] = []): T[] => arr instanceof Array ? arr : def
|
export const arrayOrDefault = <T,>(arr: unknown, def: T[] = []): T[] => arr instanceof Array ? arr : def
|
||||||
|
|
||||||
|
@ -3,6 +3,12 @@ import { AntdIconProps } from '@ant-design/icons/lib/components/AntdIcon'
|
|||||||
|
|
||||||
import { BaseDataType, ChartDataset } from '@components/d3'
|
import { BaseDataType, ChartDataset } from '@components/d3'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фабрика методов-оптимизаторов для массивов точек перед выводом на график
|
||||||
|
*
|
||||||
|
* @param isEquals Метод сравнения элементов на равенство
|
||||||
|
* @returns Метод вырезки идентичных элементов (по результатам работы переданного метода)
|
||||||
|
*/
|
||||||
export const makePointsOptimizator = <DataType extends BaseDataType>(isEquals: (a: DataType, b: DataType) => boolean) => (points: DataType[]) => {
|
export const makePointsOptimizator = <DataType extends BaseDataType>(isEquals: (a: DataType, b: DataType) => boolean) => (points: DataType[]) => {
|
||||||
if (!Array.isArray(points) || points.length < 3) return points
|
if (!Array.isArray(points) || points.length < 3) return points
|
||||||
|
|
||||||
@ -16,6 +22,22 @@ export const makePointsOptimizator = <DataType extends BaseDataType>(isEquals: (
|
|||||||
|
|
||||||
export type TouchType = 'all' | 'x' | 'y'
|
export type TouchType = 'all' | 'x' | 'y'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает расстояние между точками в зависимости от типа касания
|
||||||
|
*
|
||||||
|
* Если тип касания 'x', то вернёт модуль разницы абсцисс
|
||||||
|
*
|
||||||
|
* Если тип касания 'y', то вернёт модуль разницы ординат
|
||||||
|
*
|
||||||
|
* Иначе вернёт корень суммы квадратов разностей координат точек
|
||||||
|
*
|
||||||
|
* @param x1 Абсцисса первой точки
|
||||||
|
* @param y1 Ордината первой точки
|
||||||
|
* @param x2 Абсцисса второй точки
|
||||||
|
* @param y2 Ордината второй точки
|
||||||
|
* @param type Тип касания
|
||||||
|
* @returns Расстояние между точками
|
||||||
|
*/
|
||||||
export const getDistance = (x1: number, y1: number, x2: number, y2: number, type: TouchType = 'all') => {
|
export const getDistance = (x1: number, y1: number, x2: number, y2: number, type: TouchType = 'all') => {
|
||||||
if (type === 'x') return Math.abs(x1 - x2)
|
if (type === 'x') return Math.abs(x1 - x2)
|
||||||
if (type === 'y') return Math.abs(y1 - y2)
|
if (type === 'y') return Math.abs(y1 - y2)
|
||||||
@ -23,6 +45,13 @@ export const getDistance = (x1: number, y1: number, x2: number, y2: number, type
|
|||||||
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
|
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает иконку графика в зависимости от типа
|
||||||
|
*
|
||||||
|
* @param chart График, у которого будет проверен тип
|
||||||
|
* @param options Дополнительные опции иконки
|
||||||
|
* @returns Элемент иконки
|
||||||
|
*/
|
||||||
export const getChartIcon = <DataType extends BaseDataType>(chart: ChartDataset<DataType>, options?: Omit<AntdIconProps, 'ref'>) => {
|
export const getChartIcon = <DataType extends BaseDataType>(chart: ChartDataset<DataType>, options?: Omit<AntdIconProps, 'ref'>) => {
|
||||||
let Icon
|
let Icon
|
||||||
switch (chart.type) {
|
switch (chart.type) {
|
||||||
|
@ -16,10 +16,22 @@ export enum timeInS {
|
|||||||
week = day * 7,
|
week = day * 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка значения на возможность преобразования к дате
|
||||||
|
*
|
||||||
|
* @param value Проверяемое значение
|
||||||
|
* @returns Является ли значение потенциальной датой
|
||||||
|
*/
|
||||||
export function isRawDate(value: unknown): value is RawDate {
|
export function isRawDate(value: unknown): value is RawDate {
|
||||||
return !isNaN(Date.parse(String(value)))
|
return !isNaN(Date.parse(String(value)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка значения на возможность преобразования к `TimeDto`
|
||||||
|
*
|
||||||
|
* @param value Проверяемое значение
|
||||||
|
* @returns Является ли значение потенциальным объектом `TimeDto`
|
||||||
|
*/
|
||||||
export function isTime(value: unknown): value is TimeDto {
|
export function isTime(value: unknown): value is TimeDto {
|
||||||
if (!value || typeof value !== 'object')
|
if (!value || typeof value !== 'object')
|
||||||
return false
|
return false
|
||||||
@ -27,18 +39,40 @@ export function isTime(value: unknown): value is TimeDto {
|
|||||||
return ['hour', 'minute', 'second'].every((key) => keys.includes(key))
|
return ['hour', 'minute', 'second'].every((key) => keys.includes(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматировать значение как дату в строку
|
||||||
|
*
|
||||||
|
* @param date Форматируемое значение
|
||||||
|
* @param utc Преобразовывать ли к UTC
|
||||||
|
* @param format Формат вывода
|
||||||
|
* @returns Форматированная дата в строке
|
||||||
|
*/
|
||||||
export const formatDate = (date: unknown, utc: boolean = false, format: string = defaultFormat) => {
|
export const formatDate = (date: unknown, utc: boolean = false, format: string = defaultFormat) => {
|
||||||
if (!isRawDate(date)) return null
|
if (!isRawDate(date)) return null
|
||||||
const out = utc ? moment.utc(date).local() : moment(date)
|
const out = utc ? moment.utc(date).local() : moment(date)
|
||||||
return out.format(format)
|
return out.format(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматировать значение как время в строку
|
||||||
|
*
|
||||||
|
* @param time Форматируемое значение
|
||||||
|
* @param utc Преобразовывать ли к UTC
|
||||||
|
* @param format Формат вывода
|
||||||
|
* @returns Форматированное время в строке
|
||||||
|
*/
|
||||||
export const formatTime = (time: unknown, utc: boolean = false, format: string = defaultTimeFormat) => {
|
export const formatTime = (time: unknown, utc: boolean = false, format: string = defaultTimeFormat) => {
|
||||||
if(!isTime(time)) return
|
if(!isTime(time)) return
|
||||||
const out = timeToMoment(time, utc, format)
|
const out = timeToMoment(time, utc, format)
|
||||||
return out.format(format)
|
return out.format(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Привести секунды к строке периоду
|
||||||
|
*
|
||||||
|
* @param time Указанное кол-во секунд
|
||||||
|
* @returns Форматированная строка период
|
||||||
|
*/
|
||||||
export const periodToString = (time?: number) => {
|
export const periodToString = (time?: number) => {
|
||||||
if (!time || time <= 0) return '00:00:00'
|
if (!time || time <= 0) return '00:00:00'
|
||||||
const days = Math.floor(time / timeInS.day)
|
const days = Math.floor(time / timeInS.day)
|
||||||
@ -54,11 +88,26 @@ export const periodToString = (time?: number) => {
|
|||||||
return `${days > 0 ? days : ''} ${toFixed(hours)}:${toFixed(minutes)}:${toFixed(seconds)}`
|
return `${days > 0 ? days : ''} ${toFixed(hours)}:${toFixed(minutes)}:${toFixed(seconds)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вычислить кол-во дней между двумя датами
|
||||||
|
*
|
||||||
|
* @param start Левая дата
|
||||||
|
* @param end Правая дата
|
||||||
|
* @returns если оба аргумента потенциальны даты, то кол-во дней между ними, иначе `undefined`
|
||||||
|
*/
|
||||||
export const calcDuration = (start: unknown, end: unknown): number | undefined => {
|
export const calcDuration = (start: unknown, end: unknown): number | undefined => {
|
||||||
if (!isRawDate(start) || !isRawDate(end)) return undefined
|
if (!isRawDate(start) || !isRawDate(end)) return undefined
|
||||||
return (+new Date(end) - +new Date(start)) * timeInS.millisecond / timeInS.day
|
return (+new Date(end) - +new Date(start)) * timeInS.millisecond / timeInS.day
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сдвинуть дату на указанное время
|
||||||
|
*
|
||||||
|
* @param date Исходная дата
|
||||||
|
* @param value Коэффициент сдвига
|
||||||
|
* @param type Тип сдвига (день, час, минут и т.д.)
|
||||||
|
* @returns Смещённая дата
|
||||||
|
*/
|
||||||
export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate | null => {
|
export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate | null => {
|
||||||
if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return null
|
if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return null
|
||||||
const d = new Date(date)
|
const d = new Date(date)
|
||||||
@ -86,17 +135,41 @@ export const rawTimezones = Object.freeze({
|
|||||||
|
|
||||||
export type TimezoneId = keyof typeof rawTimezones
|
export type TimezoneId = keyof typeof rawTimezones
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, является ли переданное значение корректным ID часовой зоны
|
||||||
|
*
|
||||||
|
* @param value Проверяемое значение
|
||||||
|
* @returns Является ли переданное значение корректным ID часовой зоны
|
||||||
|
*/
|
||||||
export const isTimezoneId = (value: unknown): value is TimezoneId => !!value && String(value) in rawTimezones
|
export const isTimezoneId = (value: unknown): value is TimezoneId => !!value && String(value) in rawTimezones
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ищет часовую зону для переданной телеметрии
|
||||||
|
* @param value Данные телеметрии
|
||||||
|
* @returns Название часовой зоны
|
||||||
|
*/
|
||||||
export const findTimezoneId = (value: SimpleTimezoneDto): TimezoneId =>
|
export const findTimezoneId = (value: SimpleTimezoneDto): TimezoneId =>
|
||||||
(isTimezoneId(value.timezoneId) && value.timezoneId) ||
|
(isTimezoneId(value.timezoneId) && value.timezoneId) ||
|
||||||
(Object.keys(rawTimezones) as TimezoneId[]).find(id => rawTimezones[id] === value.hours) as TimezoneId
|
(Object.keys(rawTimezones) as TimezoneId[]).find(id => rawTimezones[id] === value.hours) as TimezoneId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Приводит `TimeDto`-объект к `Moment`-объекту
|
||||||
|
*
|
||||||
|
* @param time `TimeDto`-объект
|
||||||
|
* @param isUtc Приводить ли к UTC
|
||||||
|
* @param format Формат для обработки
|
||||||
|
* @returns `Moment`-объект
|
||||||
|
*/
|
||||||
export const timeToMoment = (time?: TimeDto | null, isUtc?: boolean, format: string = defaultTimeFormat): Moment => {
|
export const timeToMoment = (time?: TimeDto | null, isUtc?: boolean, format: string = defaultTimeFormat): Moment => {
|
||||||
const input = `${time?.hour ?? 0}:${time?.minute ?? 0}:${time?.second ?? 0}`
|
const input = `${time?.hour ?? 0}:${time?.minute ?? 0}:${time?.second ?? 0}`
|
||||||
return isUtc ? moment.utc(input, format).local() : moment(input, format)
|
return isUtc ? moment.utc(input, format).local() : moment(input, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Приводит `Moment`-объект к `TimeDto`-объекту
|
||||||
|
* @param time `Moment`-объект
|
||||||
|
* @returns `TimeDto`-объекту
|
||||||
|
*/
|
||||||
export const momentToTime = (time?: Moment | null): TimeDto => ({
|
export const momentToTime = (time?: Moment | null): TimeDto => ({
|
||||||
hour: time?.hour() ?? 0,
|
hour: time?.hour() ?? 0,
|
||||||
minute: time?.minute() ?? 0,
|
minute: time?.minute() ?? 0,
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматирует число по заданным параметрам
|
||||||
|
*
|
||||||
|
* @param number Форматируемое число
|
||||||
|
* @param def Вывод по-умолчанию
|
||||||
|
* @param fixed Длина числа после форматирования
|
||||||
|
* @returns Строка - форматированное число
|
||||||
|
*/
|
||||||
export const getPrecision = (number: number, def: string = '-', fixed: number = 2): string => Number.isFinite(number) ? number.toFixed(fixed) : def
|
export const getPrecision = (number: number, def: string = '-', fixed: number = 2): string => Number.isFinite(number) ? number.toFixed(fixed) : def
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,7 +33,12 @@ export const limitValue = <T,>(min: T, max: T) => (value: T) => {
|
|||||||
*/
|
*/
|
||||||
export const range = (end: number, start: number = 0) => Array(end - start).fill(undefined).map((_, i) => start + i)
|
export const range = (end: number, start: number = 0) => Array(end - start).fill(undefined).map((_, i) => start + i)
|
||||||
|
|
||||||
export const pretify = (n: number): number | null => {
|
/**
|
||||||
|
* Округляет число до ближайшего красивого
|
||||||
|
* @param n Округляемое число
|
||||||
|
* @returns Округлённое число
|
||||||
|
*/
|
||||||
|
export const prettify = (n: number): number | null => {
|
||||||
if (!Number.isFinite(n)) return null
|
if (!Number.isFinite(n)) return null
|
||||||
let i = 0
|
let i = 0
|
||||||
for (; Math.abs(n) >= 100; i++) n /= 10
|
for (; Math.abs(n) >= 100; i++) n /= 10
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* Копирует данные в глубину.
|
* Копирует данные с максимальной глубиной
|
||||||
*
|
*
|
||||||
* @remarks
|
* @remarks
|
||||||
* При копированиий объектов, содержащих функций может возникнуть исключение.
|
* При копирований объектов, содержащих функций может возникнуть исключение.
|
||||||
* Не предназначено для копирования функций.
|
* Не предназначено для копирования функций.
|
||||||
*
|
*
|
||||||
* @param data Копируемые данные
|
* @param data Копируемые данные
|
||||||
* @returns Полная копия `data`
|
* @returns Полная копия исходных данных
|
||||||
*/
|
*/
|
||||||
export const deepCopy = <T,>(data: T): T => JSON.parse(JSON.stringify(data ?? null))
|
export const deepCopy = <T,>(data: T): T => JSON.parse(JSON.stringify(data ?? null))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Маппинг полей объекта
|
* Аналог функции `Array.prototype.map`, но для работы с объектами
|
||||||
*
|
*
|
||||||
* @param data Входящие данные
|
* @param data Исходный объект
|
||||||
* @param handler Обработчик
|
* @param handler Метод-обработчик
|
||||||
*
|
*
|
||||||
* @returns Объект с обработанными полями
|
* @returns Объект с обработанными полями
|
||||||
*/
|
*/
|
||||||
|
@ -12,10 +12,22 @@ export type ServiceName = string
|
|||||||
export type ServiceRequestType = 'get' | 'edit' | 'delete'
|
export type ServiceRequestType = 'get' | 'edit' | 'delete'
|
||||||
export type PermissionRequest = `${ServiceName}.${ServiceRequestType}`
|
export type PermissionRequest = `${ServiceName}.${ServiceRequestType}`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка соответствует ли значение типу `ServiceRequestType`
|
||||||
|
*
|
||||||
|
* @param value Проверяемое значение
|
||||||
|
* @returns Является ли значение объектом типа `ServiceRequestType`
|
||||||
|
*/
|
||||||
export function isRequestType(value: string): value is ServiceRequestType {
|
export function isRequestType(value: string): value is ServiceRequestType {
|
||||||
return ['get', 'edit', 'delete'].includes(value)
|
return ['get', 'edit', 'delete'].includes(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерация объекта, содержащего информацию о наличии или отсутствия перечисленных разрешений
|
||||||
|
*
|
||||||
|
* @param values Список разрешений
|
||||||
|
* @returns Объект с информацией о разрешениях
|
||||||
|
*/
|
||||||
export const getPermissions = (...values: PermissionRequest[]) => {
|
export const getPermissions = (...values: PermissionRequest[]) => {
|
||||||
const permissions: Record<string, Partial<Record<ServiceRequestType, boolean>>> = {}
|
const permissions: Record<string, Partial<Record<ServiceRequestType, boolean>>> = {}
|
||||||
values.forEach((key) => {
|
values.forEach((key) => {
|
||||||
@ -27,6 +39,13 @@ export const getPermissions = (...values: PermissionRequest[]) => {
|
|||||||
return permissions
|
return permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка наличия у пользователя разрешения или списка разрешений
|
||||||
|
*
|
||||||
|
* @param permission Разрешение или список разрешений
|
||||||
|
* @param userPermissions Список разрешений пользователя (если не указано, будут получены из локального хранилища)
|
||||||
|
* @returns `true` если все разрешения присутствуют, иначе `false`
|
||||||
|
*/
|
||||||
export const hasPermission = (permission?: Permission | Permission[], userPermissions?: Permission[]): boolean => {
|
export const hasPermission = (permission?: Permission | Permission[], userPermissions?: Permission[]): boolean => {
|
||||||
if (!Array.isArray(permission) && typeof permission !== 'string')
|
if (!Array.isArray(permission) && typeof permission !== 'string')
|
||||||
return true
|
return true
|
||||||
@ -36,6 +55,13 @@ export const hasPermission = (permission?: Permission | Permission[], userPermis
|
|||||||
return permission.every((perm) => userPerms.includes(perm))
|
return permission.every((perm) => userPerms.includes(perm))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка доступности секции сайта для посещения пользователем
|
||||||
|
*
|
||||||
|
* @param section Секция сайта
|
||||||
|
* @param userPermission Разрешения пользователя
|
||||||
|
* @returns `true` если все разрешения присутствуют, иначе `false`
|
||||||
|
*/
|
||||||
const sectionAvailable = (section: PermissionRecord, userPermission: Permission[]) => {
|
const sectionAvailable = (section: PermissionRecord, userPermission: Permission[]) => {
|
||||||
for (const child of Object.values(section)) {
|
for (const child of Object.values(section)) {
|
||||||
if (!child) continue
|
if (!child) continue
|
||||||
@ -48,6 +74,12 @@ const sectionAvailable = (section: PermissionRecord, userPermission: Permission[
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка доступности URL для посещения пользователем
|
||||||
|
* @param path URL
|
||||||
|
* @param userPermissions Разрешения пользователя (если не заданы, будут получены из локального хранилища)
|
||||||
|
* @returns `true` если все разрешения присутствуют, иначе `false`
|
||||||
|
*/
|
||||||
export const isURLAvailable = (path: string, userPermissions?: Permission[]) => {
|
export const isURLAvailable = (path: string, userPermissions?: Permission[]) => {
|
||||||
if (publicPages.includes(path)) return true
|
if (publicPages.includes(path)) return true
|
||||||
|
|
||||||
@ -95,14 +127,26 @@ export const NoAccessComponent = memo(() => getUser().login ? (
|
|||||||
<Navigate to={'/login'} replace />
|
<Navigate to={'/login'} replace />
|
||||||
))
|
))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HOC добавляющий проверку на наличие разрешений для отображения компонента
|
||||||
|
* @param Component Исходных компонент
|
||||||
|
* @param requirements Необходимые разрешения
|
||||||
|
* @param elseNode Компонент по-умолчанию
|
||||||
|
* @returns Обёрнутый компонент с проверкой разрешений
|
||||||
|
*/
|
||||||
export const withPermissions = <P extends object>(
|
export const withPermissions = <P extends object>(
|
||||||
Component: NamedExoticComponent<P> | ((props: P) => ReactElement),
|
Component: NamedExoticComponent<P> | ((props: P) => ReactElement),
|
||||||
requirements: Permission[] = [],
|
requirements: Permission[] = [],
|
||||||
elseNode: JSX.Element = <NoAccessComponent />
|
elseNode: JSX.Element = <NoAccessComponent />
|
||||||
): PrivateComponent<P> => Object.assign(memo<P>(function PrivateWrapper(props) {
|
): PrivateComponent<P> => memo<P>(function PrivateWrapper(props) {
|
||||||
return hasPermission(requirements) ? <Component {...props} /> : elseNode
|
return hasPermission(requirements) ? <Component {...props} /> : elseNode
|
||||||
}))
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить url текущей выбранной вкладки
|
||||||
|
*
|
||||||
|
* @returns url текущей выбранной вкладки
|
||||||
|
*/
|
||||||
export const getTabname = () => {
|
export const getTabname = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const attr = useMemo(() => params['*']?.split('/').filter(s => s)[0] ?? null, [params])
|
const attr = useMemo(() => params['*']?.split('/').filter(s => s)[0] ?? null, [params])
|
||||||
|
@ -11,10 +11,15 @@ export type SaubData = WellOperationDto & {
|
|||||||
depth?: number
|
depth?: number
|
||||||
/** Дата */
|
/** Дата */
|
||||||
date?: string
|
date?: string
|
||||||
/** Колличество часов НПВ с начала бурения до текущего момента */
|
/** Количество часов НПВ с начала бурения до текущего момента */
|
||||||
nptHours?: number
|
nptHours?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить списки операций для конкретной скважины
|
||||||
|
* @param idWell ID скважины
|
||||||
|
* @returns Списки операций
|
||||||
|
*/
|
||||||
export const getOperations = async (idWell: number): Promise<{
|
export const getOperations = async (idWell: number): Promise<{
|
||||||
operations: WellOperationDtoPlanFactPredictBase[],
|
operations: WellOperationDtoPlanFactPredictBase[],
|
||||||
plan: SaubData[]
|
plan: SaubData[]
|
||||||
|
@ -16,12 +16,25 @@ export enum StorageNames {
|
|||||||
witsInfo = 'witsInfo'
|
witsInfo = 'witsInfo'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить массив значений из локального хранилища
|
||||||
|
*
|
||||||
|
* @param name Имя массива
|
||||||
|
* @param sep Разделитель элементов
|
||||||
|
* @returns Массив значений или `null`
|
||||||
|
*/
|
||||||
export const getArrayFromLocalStorage = <T extends string = string>(name: string, sep: string | RegExp = ','): T[] | null => {
|
export const getArrayFromLocalStorage = <T extends string = string>(name: string, sep: string | RegExp = ','): T[] | null => {
|
||||||
const raw = localStorage.getItem(name)
|
const raw = localStorage.getItem(name)
|
||||||
if (!raw) return null
|
if (!raw) return null
|
||||||
return raw.split(sep).map<T>(elm => elm as T)
|
return raw.split(sep) as T[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить объект из JSON строки в локальном хранилище
|
||||||
|
*
|
||||||
|
* @param name Имя строки
|
||||||
|
* @returns Прочитанный объект или `null`
|
||||||
|
*/
|
||||||
export const getJSON = <T,>(name: StorageNames): T | null => {
|
export const getJSON = <T,>(name: StorageNames): T | null => {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(name)
|
const raw = localStorage.getItem(name)
|
||||||
@ -32,6 +45,13 @@ export const getJSON = <T,>(name: StorageNames): T | null => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Записать объект в локальное хранилище, как JSON строка
|
||||||
|
*
|
||||||
|
* @param name Имя строки
|
||||||
|
* @param data Сохраняемый объект
|
||||||
|
* @returns `true` если сохранение успешно, иначе `false`
|
||||||
|
*/
|
||||||
export const setJSON = <T,>(name: StorageNames, data: T | null): boolean => {
|
export const setJSON = <T,>(name: StorageNames, data: T | null): boolean => {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(name, JSON.stringify(data))
|
localStorage.setItem(name, JSON.stringify(data))
|
||||||
@ -42,8 +62,17 @@ export const setJSON = <T,>(name: StorageNames, data: T | null): boolean => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить информацию о пользователе из локального хранилища
|
||||||
|
*
|
||||||
|
* @returns Объект данных о пользователе
|
||||||
|
*/
|
||||||
export const getUser = (): UserTokenDto => getJSON(StorageNames.user) || {}
|
export const getUser = (): UserTokenDto => getJSON(StorageNames.user) || {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить разрешения пользователя из локального хранилища
|
||||||
|
* @returns Список разрешений или `null`
|
||||||
|
*/
|
||||||
export const getUserPermissions = (): Permission[] | null => {
|
export const getUserPermissions = (): Permission[] | null => {
|
||||||
let permissions = getUser()?.permissions?.map((perm) => perm.name as string)
|
let permissions = getUser()?.permissions?.map((perm) => perm.name as string)
|
||||||
if (!permissions) // TODO: Удалить в следующем релизе, вставлено для совместимости
|
if (!permissions) // TODO: Удалить в следующем релизе, вставлено для совместимости
|
||||||
@ -51,11 +80,20 @@ export const getUserPermissions = (): Permission[] | null => {
|
|||||||
return permissions || null
|
return permissions || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохранить данные пользователя в локальное хранилище
|
||||||
|
*
|
||||||
|
* @param user Данные пользователя
|
||||||
|
* @returns `true` если сохранение успешно, иначе `false`
|
||||||
|
*/
|
||||||
export const setUser = (user: UserTokenDto) => {
|
export const setUser = (user: UserTokenDto) => {
|
||||||
OpenAPI.TOKEN = user.token ?? undefined
|
OpenAPI.TOKEN = user.token ?? undefined
|
||||||
localStorage.setItem(StorageNames.user, JSON.stringify(user))
|
return setJSON(StorageNames.user, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистить данные о пользователе в локальном хранилище
|
||||||
|
*/
|
||||||
export const removeUser = () => {
|
export const removeUser = () => {
|
||||||
localStorage.removeItem(StorageNames.userId)
|
localStorage.removeItem(StorageNames.userId)
|
||||||
localStorage.removeItem(StorageNames.login)
|
localStorage.removeItem(StorageNames.login)
|
||||||
@ -65,13 +103,26 @@ export const removeUser = () => {
|
|||||||
localStorage.removeItem(StorageNames.user)
|
localStorage.removeItem(StorageNames.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить объект настроек таблицы
|
||||||
|
*
|
||||||
|
* @param tableName Имя таблицы
|
||||||
|
* @returns Объект настроек таблицы
|
||||||
|
*/
|
||||||
export const getTableSettings = (tableName: string): TableSettings => {
|
export const getTableSettings = (tableName: string): TableSettings => {
|
||||||
const tables = getJSON<TableSettingsStore>(StorageNames.tableSettings) ?? {}
|
const tables = getJSON<TableSettingsStore>(StorageNames.tableSettings) ?? {}
|
||||||
if (!(tableName in tables)) return {}
|
if (!(tableName in tables)) return {}
|
||||||
return wrapValues(tables[tableName] ?? {}, normalizeTableColumn)
|
return wrapValues(tables[tableName] ?? {}, normalizeTableColumn)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setTableSettings = (tableName: string, settings?: TableSettings | null): boolean => {
|
/**
|
||||||
|
* Сохранить объект настроек таблицы
|
||||||
|
*
|
||||||
|
* @param tableName Имя таблицы
|
||||||
|
* @param settings Объект настроек
|
||||||
|
* @returns `true` если сохранение успешно, иначе `false`
|
||||||
|
*/
|
||||||
|
export const setTableSettings = (tableName: string, settings?: TableSettings | null) => {
|
||||||
const currentStore = getJSON<TableSettingsStore>(StorageNames.tableSettings) ?? {}
|
const currentStore = getJSON<TableSettingsStore>(StorageNames.tableSettings) ?? {}
|
||||||
currentStore[tableName] = wrapValues(settings ?? {}, optimizeTableColumn)
|
currentStore[tableName] = wrapValues(settings ?? {}, optimizeTableColumn)
|
||||||
return setJSON(StorageNames.tableSettings, currentStore)
|
return setJSON(StorageNames.tableSettings, currentStore)
|
||||||
@ -81,4 +132,9 @@ export type DataDashboardNNB = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить настройки панели ННБ
|
||||||
|
*
|
||||||
|
* @returns Объект настроек панели ННБ
|
||||||
|
*/
|
||||||
export const getDashboardNNB = () => getJSON<DataDashboardNNB>(StorageNames.dashboardNNB)
|
export const getDashboardNNB = () => getJSON<DataDashboardNNB>(StorageNames.dashboardNNB)
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Фабрика методов обрезки строк по словам с добавлением суффикса
|
||||||
|
*
|
||||||
|
* @param maxLength Максимальная длинна строки
|
||||||
|
* @param separator Разделитель слов в строке
|
||||||
|
* @param suffix Суффикс строки, отображаемый в случае обрезки
|
||||||
|
* @returns Метод обрезки строки
|
||||||
|
*/
|
||||||
export const makeStringCutter = (maxLength: number = 100, separator: string = ' ', suffix: string = '...') => <T,>(comment: T): T | string => {
|
export const makeStringCutter = (maxLength: number = 100, separator: string = ' ', suffix: string = '...') => <T,>(comment: T): T | string => {
|
||||||
if (!comment || typeof comment !== 'string' || comment.length <= maxLength)
|
if (!comment || typeof comment !== 'string' || comment.length <= maxLength)
|
||||||
return comment
|
return comment // Обрабатываются только строки с длинной выше максимальной
|
||||||
if (maxLength <= suffix.length)
|
if (maxLength <= suffix.length) // Если максимальная длина меньше длины суффикса вывести начало суффикса длинной `maxLength`
|
||||||
return suffix.substring(0, maxLength)
|
return suffix.substring(0, maxLength)
|
||||||
const lastSep = comment.lastIndexOf(separator, maxLength - suffix.length)
|
const lastSep = comment.lastIndexOf(separator, maxLength - suffix.length) // Ищем последнее разделение слов перед максимальной длинной с вычетом длины суффикса
|
||||||
if (lastSep < 0)
|
if (lastSep < 0) // Если разделитель не найден обрезаем само слово и добавляем суффикс
|
||||||
return comment.substring(0, maxLength - suffix.length) + suffix
|
return comment.substring(0, maxLength - suffix.length) + suffix
|
||||||
return comment.substring(0, lastSep) + suffix
|
return comment.substring(0, lastSep) + suffix // Иначе обрезаем до разделителя и добавляем суффикс
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Превращает SVG-элемент в `BLOB`
|
||||||
|
* @param svg SVG-элемент
|
||||||
|
* @returns `BLOB` строка
|
||||||
|
*/
|
||||||
export const svgToDataURL = (svg: SVGSVGElement) => {
|
export const svgToDataURL = (svg: SVGSVGElement) => {
|
||||||
const serializer = new XMLSerializer()
|
const serializer = new XMLSerializer()
|
||||||
let source = serializer.serializeToString(svg)
|
let source = serializer.serializeToString(svg)
|
||||||
|
@ -9,20 +9,32 @@ export type TableColumnSettings = {
|
|||||||
export type TableSettings = Record<string, TableColumnSettings>
|
export type TableSettings = Record<string, TableColumnSettings>
|
||||||
export type TableSettingsStore = Record<string, TableSettings | null>
|
export type TableSettingsStore = Record<string, TableSettings | null>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создаёт объект настроек таблицы исходя из массива столбцов
|
||||||
|
* @param columns Массив столбцов таблицы
|
||||||
|
* @returns Объект настроек таблицы
|
||||||
|
*/
|
||||||
export const makeTableSettings = <T extends object>(columns: TableColumns<T>): TableSettings => {
|
export const makeTableSettings = <T extends object>(columns: TableColumns<T>): TableSettings => {
|
||||||
const settings: TableSettings = {}
|
const settings: TableSettings = {}
|
||||||
columns.forEach((column) => {
|
columns.forEach((column) => {
|
||||||
if (!column.key) return
|
if (!column.key) return // Столбцы без ключей игнорируются
|
||||||
const key = String(column.key)
|
const key = String(column.key)
|
||||||
settings[key] = {
|
settings[key] = {
|
||||||
columnName: key,
|
columnName: key,
|
||||||
title: typeof column.title === 'string' ? column.title : key,
|
title: typeof column.title === 'string' ? column.title : key, // В качестве заголовка невозможно использовать `ReactNode`
|
||||||
visible: column.visible ?? true,
|
visible: column.visible ?? true, // По-умолчанию все столбцы видимые
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return settings
|
return settings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Объединяет несколько объектов настроек таблицы в один
|
||||||
|
*
|
||||||
|
* Приоритет объектом снижается с последнего влево
|
||||||
|
* @param settings Список объектов настроек
|
||||||
|
* @returns Совмещённый объект настроек
|
||||||
|
*/
|
||||||
export const mergeTableSettings = (...settings: TableSettings[]): TableSettings => {
|
export const mergeTableSettings = (...settings: TableSettings[]): TableSettings => {
|
||||||
const newSettings: TableSettings = {}
|
const newSettings: TableSettings = {}
|
||||||
for (const setting of settings) {
|
for (const setting of settings) {
|
||||||
@ -36,17 +48,36 @@ export const mergeTableSettings = (...settings: TableSettings[]): TableSettings
|
|||||||
return newSettings
|
return newSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Расширяет настройки столбца, полученные из хранилища
|
||||||
|
* @param column Настройки столбца
|
||||||
|
* @param name Имя столбца в случае его отсутствия в настройках
|
||||||
|
* @returns Расширенные настройки столбца
|
||||||
|
*/
|
||||||
export const normalizeTableColumn = (column: TableColumnSettings, name?: string): TableColumnSettings => ({
|
export const normalizeTableColumn = (column: TableColumnSettings, name?: string): TableColumnSettings => ({
|
||||||
...column,
|
...column,
|
||||||
columnName: column.columnName ?? name,
|
columnName: column.columnName ?? name,
|
||||||
visible: column.visible ?? true,
|
visible: column.visible ?? true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подготавливает настройки столбца к записи в хранилище
|
||||||
|
*
|
||||||
|
* @param column Настройки столбца
|
||||||
|
* @returns Подготовленные настройки столбца
|
||||||
|
*/
|
||||||
export const optimizeTableColumn = (column: TableColumnSettings): TableColumnSettings => ({
|
export const optimizeTableColumn = (column: TableColumnSettings): TableColumnSettings => ({
|
||||||
...column,
|
...column,
|
||||||
visible: column.visible ?? true,
|
visible: column.visible ?? true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Применяет настройки таблицы к списку столбцов
|
||||||
|
*
|
||||||
|
* @param columns Список столбцов таблицы
|
||||||
|
* @param settings Объект настройки таблицы
|
||||||
|
* @returns Список столбцов с настройками
|
||||||
|
*/
|
||||||
export const applyTableSettings = <T extends object>(columns: TableColumns<T>, settings: TableSettings): TableColumns<T> => {
|
export const applyTableSettings = <T extends object>(columns: TableColumns<T>, settings: TableSettings): TableColumns<T> => {
|
||||||
let newColumns: TableColumns<T> = columns.map((column) => ({ ...column }))
|
let newColumns: TableColumns<T> = columns.map((column) => ({ ...column }))
|
||||||
newColumns = newColumns.filter((column) => {
|
newColumns = newColumns.filter((column) => {
|
||||||
|
@ -3,7 +3,7 @@ import { useMemo } from 'react'
|
|||||||
import { ArgumentTypes } from '@utils/types'
|
import { ArgumentTypes } from '@utils/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Значение типа может быть представлено непосредственно значением либо функцией его возвращаюшей
|
* Значение типа может быть представлено непосредственно значением либо функцией его возвращающей
|
||||||
*/
|
*/
|
||||||
export type ReturnType<T> = T extends (...args: any) => infer R ? R : any
|
export type ReturnType<T> = T extends (...args: any) => infer R ? R : any
|
||||||
export type FunctionalValue<F extends Function> = ReturnType<F> | F
|
export type FunctionalValue<F extends Function> = ReturnType<F> | F
|
||||||
@ -11,7 +11,7 @@ export type FunctionalValue<F extends Function> = ReturnType<F> | F
|
|||||||
export const getFunctionalValue = <F extends Function>(value: FunctionalValue<F>) => value instanceof Function ? value : ((...args: ArgumentTypes<F>) => value) as unknown as F
|
export const getFunctionalValue = <F extends Function>(value: FunctionalValue<F>) => value instanceof Function ? value : ((...args: ArgumentTypes<F>) => value) as unknown as F
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Облегчает работу со значениями, которые могут быть представлены функциям.
|
* Облегчает работу со значениями, которые могут быть представлены функциям
|
||||||
*
|
*
|
||||||
* @param value Значение или функция его возвращающая
|
* @param value Значение или функция его возвращающая
|
||||||
* @returns Функция, вызов которой вернёт искомое значение
|
* @returns Функция, вызов которой вернёт искомое значение
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
export type TaskHandler<T> = () => T | PromiseLike<T>
|
export type TaskHandler<T> = () => T | PromiseLike<T>
|
||||||
|
|
||||||
export type Queue<T> = {
|
export type Queue<T> = {
|
||||||
|
/** Метод добавления задачи в очередь */
|
||||||
push: (task: TaskHandler<T>) => Promise<T>
|
push: (task: TaskHandler<T>) => Promise<T>
|
||||||
|
/** Длина очереди */
|
||||||
readonly length: number
|
readonly length: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фабрика очередей задач
|
||||||
|
* @returns Очередь задач
|
||||||
|
*/
|
||||||
export const makeTaskQueue = <T,>(): Queue<T> => {
|
export const makeTaskQueue = <T,>(): Queue<T> => {
|
||||||
let pending: Promise<T | void> = Promise.resolve()
|
let pending: Promise<T | void> = Promise.resolve()
|
||||||
let count: number = 0
|
let count: number = 0
|
||||||
|
Loading…
Reference in New Issue
Block a user