asb_cloud_front/src/components/selectors/WellTreeSelector.tsx

190 lines
7.1 KiB
TypeScript
Executable File

import { Drawer, Tree, TreeDataNode, TreeProps } from 'antd'
import { useState, useEffect, useCallback, memo, Key, useMemo } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useDepositList } from '@asb/context'
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 '@styles/components/well_tree_select.css'
/**
* Для поиска в URL текущего раздела по шаблону `/{type}/{id}`
*
* Если найдено совпадение может вернуть 1 или 2 группы соответственно
*/
const URL_REGEX = /^\/([^\/?#]+)(?:\/([^\/?#]+))?/
export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown'
export const checkIsWellOnline = (lastTelemetryDate: unknown): boolean =>
isRawDate(lastTelemetryDate) && (Date.now() - +new Date(lastTelemetryDate) < 600_000)
const getKeyByUrl = (url?: string): [Key | null, string | null, number | null] => {
const result = url?.match(URL_REGEX) // pattern "/:type/:id"
if (!result) return [null, null, null]
return [result[0], result[1], result[2] && result[2] !== 'null' ? Number(result[2]) : null]
}
const getLabel = (wellsTree: TreeDataNode[], value?: string): string | undefined => {
const [url, type, key] = getKeyByUrl(value)
if (!url) return
let deposit: TreeDataNode | undefined
let cluster: TreeDataNode | undefined
let well: TreeDataNode | undefined
switch (type) {
case 'deposit':
if (key === null) return 'Месторождение не выбрано'
deposit = wellsTree.find((deposit) => deposit.key === url)
if (deposit)
return `${deposit.title}`
return 'Ошибка! Месторождение не найдено!'
case 'cluster':
if (key === null) return 'Куст не выбран'
deposit = wellsTree.find((deposit) => (
cluster = deposit.children?.find((cluster: TreeDataNode) => cluster.key === url)
))
if (deposit && cluster)
return `${deposit.title} / ${cluster.title}`
return 'Ошибка! Куст не найден!'
case 'well':
if (key === null) return 'Скважина не выбрана'
deposit = wellsTree.find((deposit) => (
cluster = deposit.children?.find((cluster: TreeDataNode) => (
well = cluster.children?.find((well: TreeDataNode) => well.key === url)
))
))
if (deposit && cluster && well)
return `${deposit.title} / ${cluster.title} / ${well.title}`
return 'Ошибка! Скважина не найдена!'
default: break
}
}
const getWellSortScore = (well: WellDto) => {
let out = [1, 2, 0][well.idState ?? 2]
const timeout = Date.now() - +new Date(well.lastTelemetryDate || 0)
if (timeout < 600_000) out += 600_000 - timeout
return out
}
const sortWellsByActive = (a: WellDto, b: WellDto): number => {
const score = getWellSortScore(b) - getWellSortScore(a)
if (score !== 0) return score
return (a.caption || '')?.localeCompare(b.caption || '')
}
export type WellTreeSelectorProps = TreeProps<TreeDataNode> & {
show?: boolean
expand?: boolean | Key[]
current?: Key
onClose?: () => void
onChange?: (value: string | undefined) => void
open?: boolean
}
const getExpandKeys = (treeData: TreeDataNode[], depositKeys?: Key[] | boolean): Key[] => {
const out: Key[] = []
treeData.forEach((deposit) => {
if (Array.isArray(depositKeys) && !depositKeys.includes(deposit.key)) return
if (deposit.key) out.push(deposit.key)
deposit.children?.forEach((cluster) => {
if (cluster.key) out.push(cluster.key)
})
})
return out
}
const makeWellsTreeData = (deposits: DepositDto[]): TreeDataNode[] => deposits.map(deposit =>({
title: deposit.caption,
key: `/deposit/${deposit.id}`,
value: `/deposit/${deposit.id}`,
icon: <DepositIcon width={24} height={24}/>,
children: deposit.clusters?.map(cluster => {
const wells = cluster.wells ? cluster.wells.slice() : []
wells.sort(sortWellsByActive)
return {
title: cluster.caption,
key: `/cluster/${cluster.id}`,
value: `/cluster/${cluster.id}`,
icon: <ClusterIcon width={24} height={24}/>,
children: wells.map(well => ({
title: well.caption,
key: `/well/${well.id}`,
value: `/well/${well.id}`,
icon: <WellIcon
width={24}
height={24}
state={getWellState(well.idState)}
online={checkIsWellOnline(well.lastTelemetryDate)}
/>
})),
}
}),
}))
export const WellTreeSelector = memo<WellTreeSelectorProps>(({ expand, current, onChange, onClose, open, ...other }) => {
const [expanded, setExpanded] = useState<Key[]>([])
const [selected, setSelected] = useState<Key[]>([])
const navigate = useNavigate()
const location = useLocation()
const deposits = useDepositList()
const wellsTree = useMemo(() => makeWellsTreeData(deposits), [deposits])
const onValueChange = useCallback((value?: string): void => {
const key = getKeyByUrl(value)[0]
setSelected(key ? [key] : [])
onChange?.(getLabel(wellsTree, value))
}, [wellsTree])
const onSelect = useCallback((value: Key[]): void => {
const newRoot = URL_REGEX.exec(String(value))
const oldRoot = URL_REGEX.exec(location.pathname)
if (!newRoot || !oldRoot) return
let newPath = newRoot[0]
if (oldRoot[1] === newRoot[1]) {
/// Если типы страницы одинаковые (deposit, cluster, well), добавляем остаток старого пути
const url = location.pathname.substring(oldRoot[0].length)
newPath = newPath + url
}
navigate(newPath, { state: { from: location.pathname }})
}, [navigate, location])
useEffect(() => {
if (current) setSelected([current])
}, [current])
useEffect(() => {
setExpanded((prev) => expand ? getExpandKeys(wellsTree, expand) : prev)
}, [wellsTree, expand])
useEffect(() => onValueChange(location.pathname), [onValueChange, location.pathname])
return (
<Drawer open={open} mask={false} onClose={onClose} title={'Список скважин'}>
<Tree
{...other}
showIcon
selectedKeys={selected}
treeData={wellsTree}
onSelect={onSelect}
onExpand={setExpanded}
expandedKeys={expanded}
/>
</Drawer>
)
})
export default WellTreeSelector