* Выделен стиль блока фильтрации

* Исправлена работа страница "Наработка"
This commit is contained in:
goodmice 2022-10-06 14:37:39 +05:00
parent 39c1289d32
commit 7f2d337b4f
No known key found for this signature in database
GPG Key ID: 63EA771203189CF1
7 changed files with 156 additions and 178 deletions

View File

@ -11,6 +11,7 @@ import { makeColumn, makeDateColumn, makeNumericColumn, makeNumericSorter, makeT
import { wrapPrivateComponent } from '@utils' import { wrapPrivateComponent } from '@utils'
import { MessageService } from '@api' import { MessageService } from '@api'
import '@styles/filter.less'
import '@styles/message.less' import '@styles/message.less'
const pageSize = 26 const pageSize = 26

View File

@ -0,0 +1,128 @@
import { memo, useEffect, useMemo, useState } from 'react'
import { Select } from 'antd'
import moment from 'moment'
import { useWell } from '@asb/context'
import LoaderPortal from '@components/LoaderPortal'
import { invokeWebApiWrapperAsync } from '@components/factory'
import D3HorizontalPercentChart from '@components/d3/D3HorizontalPercentChart'
import { DateRangeWrapper, makeColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table'
import { arrayOrDefault, range, wrapPrivateComponent } from '@utils'
import { SubsystemOperationTimeService } from '@api'
import '@styles/filter.less'
import '@styles/operation_time.less'
const subsystemColors = [
'#1abc9c', '#16a085', '#2ecc71', '#27ae60',
'#3498db', '#2980b9', '#9b59b6', '#8e44ad',
'#34495e', '#2c3e50', '#f1c40f', '#f39c12',
'#e67e22', '#d35400', '#e74c3c', '#c0392b',
'#ecf0f1', '#bdc3c7', '#95a5a6', '#7f8c8d',
]
const tableColumns = [
makeColumn('Цвет', 'color', { width: 50, render: (backgroundColor) => (
<div className={'table_color'} style={{ backgroundColor }} />
)}),
makeTextColumn('Подсистема', 'subsystemName'),
makeNumericColumn('Использование, %', 'kUsage', undefined, undefined, val => (+val * 100).toFixed(2)),
makeNumericColumn('Проходка, м', 'sumDepthInterval'),
makeNumericColumn('Время работы, ч', 'usedTimeHours'),
makeNumericColumn('Кол-во запусков', 'operationCount', undefined, undefined, makeNumericRender(0)),
]
// Выбор доступен только до текущей даты
const disabledDates = (current) => current && moment(current).isAfter(moment(), 'day', '[]')
// Выбор доступен только до текущего времени
const disabledTimes = (date) => ({
disabledHours: () => range(24).filter(h => date && moment(date).hours(h).isAfter(moment(), 'hour', '[]')),
disabledMinutes: () => range(60).filter(m => date && moment(date).minutes(m).isAfter(moment(), 'minute', '[]')),
disabledSeconds: () => range(60).filter(s => date && moment(date).seconds(s).isBetween(moment(), 'second', '[]'))
})
export const OperationTime = memo(() => {
const [showLoader, setShowLoader] = useState(false)
const [data, setData] = useState([])
const [selected, setSelected] = useState([])
const [dateRange, setDateRange] = useState([])
const [well] = useWell()
// Создаём массив пунктов для селектора подсистем
const typeOptions = useMemo(() => data.map((d) => ({ label: d.subsystemName, value: d.idSubsystem })), [data])
// Фильтруем данные по выбранным подсистемам
const selectedData = useMemo(() => data.filter((d) => selected.includes(d.idSubsystem)), [selected, data])
// Подготавливаем данные для отображения на графике
const chartData = useMemo(() => selectedData.map((d) => ({
name: d.subsystemName,
percent: d.kUsage * 100,
color: d.color,
})), [selectedData])
useEffect(() => {
invokeWebApiWrapperAsync(
async () => {
if (!well.id) return
// Ограничение задаётся только если выбраны обе даты
const startDate = dateRange[1] ? dateRange[0]?.toISOString() : undefined
const endDate = dateRange[1]?.toISOString()
const data = await SubsystemOperationTimeService.getStat(well.id, undefined, startDate, endDate)
// Выбираем цвета для подсистем (если цветов не хватает начинаем сначала)
const coloredData = arrayOrDefault(data).map((d, i) => ({ ...d, color: subsystemColors[i % subsystemColors.length] }))
setData(coloredData)
setSelected(data.map((d) => d.idSubsystem)) // По-умолчанию выбираем все подсистемы
},
setShowLoader,
`Не удалось загрузить данные`,
{ actionName: 'Получение данных по скважине', well }
)
}, [dateRange, well])
return (
<LoaderPortal show={showLoader}>
<div className={'filter-group'}>
<h3 className={'head'}>Фильтр подсистем</h3>
<div className={'body'}>
<Select
allowClear
mode={'multiple'}
options={typeOptions}
className={'filter-selector'}
onChange={setSelected}
value={selected}
/>
<DateRangeWrapper
onCalendarChange={setDateRange}
disabledDate={disabledDates}
disabledTime={disabledTimes}
value={dateRange}
/>
</div>
</div>
<D3HorizontalPercentChart
data={chartData}
colors={subsystemColors}
width={'100%'}
height={'50vh'}
/>
<Table
bordered
size={'small'}
columns={tableColumns}
dataSource={selectedData}
scroll={{ y: '25vh', x: true }}
pagination={false}
/>
</LoaderPortal>
)
})
export default wrapPrivateComponent(OperationTime, {
requirements: [], // SubsystemOperationTime.get
title: 'Наработка',
route: 'operation_time',
key: 'operation_time',
})

View File

@ -1,153 +0,0 @@
import React, { ReactNode, useEffect, useState } from 'react'
import { Col, Row, Select } from 'antd'
import { Moment } from 'moment'
import { DateRangeWrapper, makeColumn, makeNumericRender, Table } from '@components/Table'
import LoaderPortal from '@components/LoaderPortal'
import { arrayOrDefault, wrapPrivateComponent } from '@utils'
import D3HorizontalChart from '@components/d3/D3HorizontalChart'
import { useWell } from '@asb/context'
import { invokeWebApiWrapperAsync } from '@components/factory'
import { SubsystemOperationTimeService } from '@api'
const { Option } = Select;
type DataType = {
idSubsystem: number
subsystemName: string
usedTimeHours: number
kUsage: number
sumDepthInterval: number
operationCount: number
}
const subsystemColors = [
'#1abc9c',
'#16a085',
'#2ecc71',
'#27ae60',
'#3498db',
'#2980b9',
'#9b59b6',
'#8e44ad',
'#34495e',
'#2c3e50',
'#f1c40f',
'#f39c12',
'#e67e22',
'#d35400',
'#e74c3c',
'#c0392b',
'#ecf0f1',
'#bdc3c7',
'#95a5a6',
'#7f8c8d',
]
const tableColumns = [
makeColumn('Цвет', 'color', { width: 50, render: (color) => (
<div style={{ backgroundColor: color, padding: '5px 0' }} />
) }),
makeColumn('Подсистема', 'subsystemName'),
makeColumn('% использования', 'kUsage', { render: val => (+val * 100).toFixed(2) }),
makeColumn('Проходка, м', 'sumDepthInterval', {render: makeNumericRender(2)}),
makeColumn('Время работы, ч', 'usedTimeHours', {render: makeNumericRender(2)}),
makeColumn('Кол-во запусков', 'operationCount'),
]
const OperationTime = () => {
const [showLoader, setShowLoader] = useState(false)
const [data, setData] = useState<DataType[]>([])
const [dateRange, setDateRange] = useState<Moment[]>([])
const [childrenData, setChildrenData] = useState<ReactNode[]>([])
const [well] = useWell()
const errorNotifyText = `Не удалось загрузить данные`
useEffect(() => {
invokeWebApiWrapperAsync(
async () => {
if (!well.id) return
try {
setData(arrayOrDefault(await SubsystemOperationTimeService.getStat(
well.id,
undefined,
dateRange[1] ? dateRange[0]?.toISOString() : undefined,
dateRange[1]?.toISOString(),
)))
} catch(e) {
setData([])
throw e
}
},
setShowLoader,
errorNotifyText,
{ actionName: 'Получение данных по скважине', well }
)
}, [dateRange])
useEffect(() => {
setChildrenData(data.map((item) => (
<Option key={item.subsystemName}>
{item.subsystemName}
</Option>
)))
}, [data])
const selectChange = (value: string[]) => {
setData(data.reduce((previousValue: DataType[], currentValue) => {
if (value.includes(currentValue.subsystemName)) {
previousValue.push(currentValue)
}
return previousValue
}, []))
}
return (
<LoaderPortal show={showLoader}>
<h3 className={'filter-group-heading'}>Фильтр подсистем</h3>
<Row align={'middle'} justify={'space-between'} wrap={false} style={{ backgroundColor: 'white' }}>
<Col span={18}>
<Select
mode="multiple"
defaultValue={data.map(d => d.subsystemName)}
onChange={selectChange}
style={{ width: '100%' }}
>
{childrenData}
</Select>
</Col>
<Col span={6}>
<DateRangeWrapper
onCalendarChange={(dateRange: any) => setDateRange(dateRange)}
value={[dateRange[0], dateRange[1]]}
/>
</Col>
</Row>
<div style={{width: '100%', height: '50vh'}}>
<D3HorizontalChart
data={data.map(item => ({name: item.subsystemName, percent: item.kUsage * 100}))}
colors={subsystemColors}
width={'100%'}
height={'50vh'}
/>
</div>
<Table
size={'small'}
columns={tableColumns}
dataSource={data.map((d, i) => ({...d, color: subsystemColors[i]}))}
scroll={{ y: '25vh', x: true }}
pagination={false}
/>
</LoaderPortal>
)
}
export default wrapPrivateComponent(OperationTime, {
requirements: [],
title: 'Наработка',
route: 'operation_time',
})

View File

@ -116,9 +116,9 @@
} }
} }
.grid-line line { .grid-line:not(:first-of-type):not(:last-of-type) line {
stroke: #ddd; stroke: #ddd;
stroke-dasharray: 4 stroke-dasharray: 4;
} }
@media (max-width: 1800px) { @media (max-width: 1800px) {

22
src/styles/filter.less Normal file
View File

@ -0,0 +1,22 @@
.filter-group {
margin: 0 0 5px 0;
& .head {
margin: 5px auto;
align-items: center;
}
& .body {
width: 100%;
display: flex;
gap: 5px;
& .type-filter {
width: 25%;
}
& .filter-selector {
flex: 1;
}
}
}

View File

@ -39,29 +39,6 @@
background: #505060; background: #505060;
} }
.filter-group {
margin: 0 0 5px 0;
& .head {
margin: 5px auto;
align-items: center;
}
& .body {
width: 100%;
display: flex;
gap: 5px;
& .type-filter {
width: 25%;
}
& .filter-selector {
flex: 1;
}
}
}
td.ant-table-column-sort { td.ant-table-column-sort {
color: black; color: black;
background-color: #fafafa; background-color: #fafafa;

View File

@ -0,0 +1,3 @@
.table_color {
padding: 5px 0;
}