From c8050b91e55a0b2c06deff7248e4d2806bbedee0 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 23 Aug 2022 17:44:02 +0500 Subject: [PATCH 01/72] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BD=D0=B0=D0=BB=D0=BE=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D1=80=D0=B8=D0=B9=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=20=D1=81=D0=BA=D0=B2=D0=B0=D0=B6=D0=B8=D0=BD=D1=8B?= =?UTF-8?q?=20*=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/factory.tsx | 29 +++++++++++++-------- src/pages/Telemetry/TelemetryView/index.jsx | 12 ++++++--- src/pages/Well.jsx | 4 ++- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/components/factory.tsx b/src/components/factory.tsx index 80ad3aa..66efdcf 100644 --- a/src/components/factory.tsx +++ b/src/components/factory.tsx @@ -53,7 +53,7 @@ export const copyToClipboard = (value: string, successText?: string, errorText?: } } -type asyncFunction = (...args: any) => Promise +type asyncFunction = (signal: AbortSignal, ...args: any) => Promise type InvokeOptions = { actionName?: string, @@ -75,23 +75,30 @@ const parseApiEror = (err: unknown, options?: InvokeOptions) => { } } -export const invokeWebApiWrapperAsync = async ( +export const invokeWebApiWrapperAsync = ( funcAsync: asyncFunction, setShowLoader?: Dispatch>, errorNotifyText?: FunctionalValue<(err: unknown) => ReactNode>, options?: InvokeOptions, ) => { + const controller = new AbortController() + const signal = controller.signal + setShowLoader?.(true) - try{ - await funcAsync() - } catch (ex) { - if(isDev()) - console.error(ex) - if (!parseApiEror(ex, options)) + funcAsync(signal) + .then((data) => { + if (data !== false) + setShowLoader?.(false) + }) + .catch((ex) => { + setShowLoader?.(false) + if(isDev()) + console.error(ex) + if (parseApiEror(ex, options)) return notify(getFunctionalValue(errorNotifyText)(ex), 'error', options?.well) - } finally { - setShowLoader?.(false) - } + }) + + return () => controller.abort() } export const download = async (url: string, fileName?: string) => { diff --git a/src/pages/Telemetry/TelemetryView/index.jsx b/src/pages/Telemetry/TelemetryView/index.jsx index c0c0242..3b37f3b 100755 --- a/src/pages/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Telemetry/TelemetryView/index.jsx @@ -143,6 +143,7 @@ const TelemetryView = memo(() => { const [dataSpin, setDataSpin] = useState([]) const [chartInterval, setChartInterval] = useState(defaultPeriod) const [showLoader, setShowLoader] = useState(false) + const [isDataLoading, setIsDataLoading] = useState(false) const [flowChartData, setFlowChartData] = useState([]) const [rop, setRop] = useState(null) const [domain, setDomain] = useState({}) @@ -183,19 +184,23 @@ const TelemetryView = memo(() => { }, [spinSubject$]) useEffect(() => { - invokeWebApiWrapperAsync( - async () => { + const cancel = invokeWebApiWrapperAsync( + async (signal) => { + if (signal.aborted) return false const flowChart = await DrillFlowChartService.getByIdWell(well.id) const dataSaub = await TelemetryDataSaubService.getData(well.id, null, chartInterval) const dataSpin = await TelemetryDataSpinService.getData(well.id, null, chartInterval) + if (signal.aborted) return false setFlowChartData(flowChart ?? []) handleDataSaub(dataSaub) handleDataSpin(dataSpin) }, - null, + setIsDataLoading, `Не удалось получить данные`, { actionName: 'Получение данных по скважине', well } ) + + return () => cancel() }, [well, chartInterval, handleDataSpin, handleDataSaub]) useEffect(() => { @@ -282,6 +287,7 @@ const TelemetryView = memo(() => { { { actionName: 'Изменение данных скважины', well } ), [well]) + const wellContext = useMemo(() => [well, updateWell], [well, updateWell]) + return ( @@ -70,7 +72,7 @@ const Well = memo(() => { } /> - + From bd7c2842c584b02ecea72999e9ee34ed47002416 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 12 Sep 2022 12:40:59 +0500 Subject: [PATCH 02/72] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B8=20=D0=BE=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/WellOperations/ImportOperations.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/WellOperations/ImportOperations.jsx b/src/pages/WellOperations/ImportOperations.jsx index cc0062b..304be1a 100755 --- a/src/pages/WellOperations/ImportOperations.jsx +++ b/src/pages/WellOperations/ImportOperations.jsx @@ -6,7 +6,7 @@ import { ErrorFetch } from '@components/ErrorFetch' import { UploadForm } from '@components/UploadForm' const errorTextStyle = { color: 'red', fontWeight: 'bold' } -const uploadFormStyle = { marginTop: '24px' } +const uploadFormStyle = { marginTop: 24 } export const ImportOperations = memo(({ well: givenWell, onDone }) => { const [deleteBeforeImport, setDeleteBeforeImport] = useState(false) @@ -15,7 +15,7 @@ export const ImportOperations = memo(({ well: givenWell, onDone }) => { const [wellContext] = useWell() const well = useMemo(() => givenWell ?? wellContext, [givenWell, wellContext]) - const url = useMemo(() => `/api/well/${well.id}/wellOperations/import${deleteBeforeImport ? '/1' : '/0'}`, [well]) + const url = useMemo(() => `/api/well/${well.id}/wellOperations/import/${deleteBeforeImport ? 1 : 0}`, [well, deleteBeforeImport]) const onUploadSuccess = useCallback(() => { setErrorText('') From eb8cf1f1d425b238396567f4004f34f06c325c28 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 12 Sep 2022 13:21:37 +0500 Subject: [PATCH 03/72] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20=D0=B8=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=BE=D1=82=D0=B8=D0=BF=20=D0=B2=20=D1=88?= =?UTF-8?q?=D0=B0=D0=BF=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 2 +- src/images/Logo.tsx | 8 +++++--- src/images/dd_logo_white_opt.svg | 1 + src/styles/App.less | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 src/images/dd_logo_white_opt.svg diff --git a/public/index.html b/public/index.html index cd94b2e..492d903 100755 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ - АСБ Vision + DDrilling diff --git a/src/images/Logo.tsx b/src/images/Logo.tsx index 88e8540..216a81b 100755 --- a/src/images/Logo.tsx +++ b/src/images/Logo.tsx @@ -1,9 +1,11 @@ import { memo } from 'react' -import logo from '@images/logo_32.png' +import { ReactComponent as AsbLogo } from '@images/dd_logo_white_opt.svg' -export const Logo = memo, HTMLImageElement>>((props) => ( - {'АСБ'} +export type LogoProps = React.SVGProps & { size?: number } + +export const Logo = memo(({ size = 200, ...props }) => ( + )) export default Logo diff --git a/src/images/dd_logo_white_opt.svg b/src/images/dd_logo_white_opt.svg new file mode 100644 index 0000000..d19dc00 --- /dev/null +++ b/src/images/dd_logo_white_opt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/styles/App.less b/src/styles/App.less index cc55b30..0b8e681 100755 --- a/src/styles/App.less +++ b/src/styles/App.less @@ -74,7 +74,7 @@ html { .header .title{ flex-grow: 1; color: #fff; - padding-left: 450px; + padding-left: calc(100vw / 2 - 400px); } .header button{ From 17ccecb2dd2b1fe110a81e070151e51b38f886e0 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 31 Oct 2022 05:12:33 +0500 Subject: [PATCH 04/72] =?UTF-8?q?=D0=92=D1=8B=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B3?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B4=D0=B0?= =?UTF-8?q?=D1=82=D0=B0=D1=81=D0=B5=D1=82=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Well/Telemetry/Archive/index.jsx | 15 +- .../Well/Telemetry/TelemetryView/dataset.js | 77 +++ .../Well/Telemetry/TelemetryView/index.jsx | 437 +++++++----------- 3 files changed, 259 insertions(+), 270 deletions(-) create mode 100644 src/pages/Well/Telemetry/TelemetryView/dataset.js diff --git a/src/pages/Well/Telemetry/Archive/index.jsx b/src/pages/Well/Telemetry/Archive/index.jsx index b15afdf..ce3d94a 100644 --- a/src/pages/Well/Telemetry/Archive/index.jsx +++ b/src/pages/Well/Telemetry/Archive/index.jsx @@ -14,8 +14,9 @@ import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker' import { formatDate, range, withPermissions } from '@utils' import { TelemetryDataSaubService } from '@api' -import { makeChartGroups, normalizeData, yAxis } from '../TelemetryView' +import { normalizeData } from '../TelemetryView' import cursorRender from '../TelemetryView/cursorRender' +import { makeChartGroups, yAxis } from '../TelemetryView/dataset' const DATA_COUNT = 2048 // Колличество точек на подгрузку графика const ADDITIVE_PAGES = 2 // Дополнительные данные для графиков @@ -108,10 +109,10 @@ const Archive = memo(() => { const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() }) const [showLoader, setShowLoader] = useState(false) const [loaded, setLoaded] = useState(null) - + const [well] = useWell() const [search, setSearchParams] = useSearchParams() - + const getInitialRange = useCallback(() => parseInt(search.get('range') ?? defaultPeriod) * 1000, [search]) const [scrollPercent, setScrollPercent] = useState(0.15) @@ -181,7 +182,7 @@ const Archive = memo(() => { }, [well]) useEffect(() => { - setStartDate((prev) => new Date(Math.max(dateLimit.from, Math.min(+prev, +dateLimit.to - chartInterval)))) + setStartDate((prev) => new Date(Math.max(dateLimit.from, Math.min(+prev, +dateLimit.to - chartInterval)))) }, [chartInterval, dateLimit]) useEffect(() => { @@ -191,11 +192,11 @@ const Archive = memo(() => { invokeWebApiWrapperAsync( async () => { const data = await TelemetryDataSaubService.getData(well.id, loadingStartDate.toISOString(), loadingInterval, DATA_COUNT) - + const loadedStartDate = new Date(Math.max(+newLoaded.start, +startDate - chartInterval * ADDITIVE_PAGES)) const loadedEndDate = new Date(Math.min(+newLoaded.end, +startDate + chartInterval * (ADDITIVE_PAGES + 1))) setLoaded({ start: loadedStartDate, end: loadedEndDate }) - + if (data) { data.forEach(elm => elm.date = new Date(elm.date)) setDataSaub((prevDataSaub) => { @@ -204,7 +205,7 @@ const Archive = memo(() => { return cutData(newData, loadedStartDate, loadedEndDate) }) } - + }, setShowLoader, `Не удалось загрузить данные c ${formatDate(startDate)} по ${formatDate(+startDate + chartInterval)}`, diff --git a/src/pages/Well/Telemetry/TelemetryView/dataset.js b/src/pages/Well/Telemetry/TelemetryView/dataset.js new file mode 100644 index 0000000..3f96262 --- /dev/null +++ b/src/pages/Well/Telemetry/TelemetryView/dataset.js @@ -0,0 +1,77 @@ +import { formatDate } from "@utils" + +export const yAxis = { + type: 'time', + accessor: (d) => new Date(d.date), + format: (d) => formatDate(d, undefined, 'DD.MM.YYYY HH:mm:ss'), +} + +const dash = [7, 3] + +const makeDataset = (label, shortLabel, color, key, unit, other) => ({ + key, + label, + shortLabel, + color, + xAxis: { + type: 'linear', + accessor: key, + unit, + }, + type: 'line', + ...other, +}) + +export const makeChartGroups = (flowChart) => { + const makeAreaOptions = (accessor) => ({ + type: 'rect_area', + data: flowChart, + hideLabel: true, + yAxis: { + type: 'linear', + accessor: 'depth', + }, + minXAccessor: 'depthStart', + maxXAccessor: 'depthEnd', + minYAccessor: accessor + 'Min', + maxYAccessor: accessor + 'Max', + linkedTo: accessor, + }) + + return [ + [ + makeDataset('Высота блока', 'Высота ТБ','#303030', 'blockPosition', 'м'), + makeDataset('Глубина скважины', 'Глубина скв','#7789A1', 'wellDepth', 'м', { dash }), + makeDataset('Расход', 'Расход','#007070', 'flow', 'л/с'), + makeDataset('Положение долота', 'Долото','#B39D59', 'bitPosition', 'м'), + makeDataset('Расход', 'Расход','#007070', 'flowMM', 'л/с', makeAreaOptions('flow')), + ], [ + makeDataset('Скорость блока', 'Скорость ТБ','#59B359', 'blockSpeed', 'м/ч'), + makeDataset('Скорость заданная', 'Скор зад-я','#95B359', 'blockSpeedSp', 'м/ч', { dash }), + ], [ + makeDataset('Давление', 'Давл','#FF0000', 'pressure', 'атм'), + makeDataset('Давление заданное', 'Давл зад-е','#CC0000', 'pressureSp', 'атм'), + makeDataset('Давление ХХ', 'Давл ХХ','#CC4429', 'pressureIdle', 'атм', { dash }), + makeDataset('Перепад давления МАКС', 'ΔР макс','#B34A36', 'pressureDeltaLimitMax', 'атм', { dash }), + makeDataset('Давление', 'Давл','#FF0000', 'pressureMM', 'атм', makeAreaOptions('pressure')), + ], [ + makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoad', 'т'), + makeDataset('Осевая нагрузка заданная', 'Нагр зад-я','#3D6DCC', 'axialLoadSp', 'т', { dash }), + makeDataset('Осевая нагрузка МАКС', 'Нагр макс','#3D3DCC', 'axialLoadLimitMax', 'т', { dash }), + makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoadMM', 'т', makeAreaOptions('axialLoad')), + ], [ + makeDataset('Вес на крюке', 'Вес на крюке','#00B3B3', 'hookWeight', 'т'), + makeDataset('Вес инструмента ХХ', 'Вес инст ХХ','#29CCB1', 'hookWeightIdle', 'т', { dash }), + makeDataset('Вес инструмента МИН', 'Вес инст мин','#47A1B3', 'hookWeightLimitMin', 'т', { dash }), + makeDataset('Вес инструмента МАКС', 'Вес инст мах','#2D7280', 'hookWeightLimitMax', 'т', { dash }), + makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeed', 'об/мин'), + makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeedMM', 'об/мин', makeAreaOptions('rotorSpeed')), + ], [ + makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorque', 'кН·м'), + makeDataset('План. Момент на роторе', 'Момент зад-й','#9629CC', 'rotorTorqueSp', 'кН·м', { dash }), + makeDataset('Момент на роторе ХХ', 'Момент ХХ','#CC2996', 'rotorTorqueIdle', 'кН·м', { dash }), + makeDataset('Момент МАКС.', 'Момент макс','#FF00FF', 'rotorTorqueLimitMax', 'кН·м', { dash }), + makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorqueMM', 'кН·м', makeAreaOptions('rotorTorque')), + ] + ] +} diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index 7a2ca50..1c5c4f9 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -12,12 +12,13 @@ import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker' import { formatDate, hasPermission, withPermissions } from '@utils' import { Subscribe } from '@services/signalr' import { - DrillFlowChartService, - OperationStatService, - TelemetryDataSaubService, - TelemetryDataSpinService + DrillFlowChartService, + OperationStatService, + TelemetryDataSaubService, + TelemetryDataSpinService } from '@api' +import { makeChartGroups, yAxis } from './dataset' import ActiveMessagesOnline from './ActiveMessagesOnline' import WirelineRunOut from './WirelineRunOut' import { CustomColumn } from './CustomColumn' @@ -36,286 +37,196 @@ import '@styles/message.less' const { Option } = Select -export const yAxis = { - type: 'time', - accessor: (d) => new Date(d.date), - format: (d) => formatDate(d, undefined, 'DD.MM.YYYY HH:mm:ss'), -} - -const dash = [7, 3] - -const makeDataset = (label, shortLabel, color, key, unit, other) => ({ - key, - label, - shortLabel, - color, - xAxis: { - type: 'linear', - accessor: key, - unit, - }, - type: 'line', - ...other, -}) - -export const makeChartGroups = (flowChart) => { - const makeAreaOptions = (accessor) => ({ - type: 'rect_area', - data: flowChart, - hideLabel: true, - yAxis: { - type: 'linear', - accessor: 'depth', - }, - minXAccessor: 'depthStart', - maxXAccessor: 'depthEnd', - minYAccessor: accessor + 'Min', - maxYAccessor: accessor + 'Max', - linkedTo: accessor, - }) - - return [ - [ - makeDataset('Высота блока', 'Высота ТБ','#303030', 'blockPosition', 'м'), - makeDataset('Глубина скважины', 'Глубина скв','#7789A1', 'wellDepth', 'м', { dash }), - makeDataset('Расход', 'Расход','#007070', 'flow', 'л/с'), - makeDataset('Положение долота', 'Долото','#B39D59', 'bitPosition', 'м'), - makeDataset('Расход', 'Расход','#007070', 'flowMM', 'л/с', makeAreaOptions('flow')), - ], [ - makeDataset('Скорость блока', 'Скорость ТБ','#59B359', 'blockSpeed', 'м/ч'), - makeDataset('Скорость заданная', 'Скор зад-я','#95B359', 'blockSpeedSp', 'м/ч', { dash }), - ], [ - makeDataset('Давление', 'Давл','#FF0000', 'pressure', 'атм'), - makeDataset('Давление заданное', 'Давл зад-е','#CC0000', 'pressureSp', 'атм'), - makeDataset('Давление ХХ', 'Давл ХХ','#CC4429', 'pressureIdle', 'атм', { dash }), - makeDataset('Перепад давления МАКС', 'ΔР макс','#B34A36', 'pressureDeltaLimitMax', 'атм', { dash }), - makeDataset('Давление', 'Давл','#FF0000', 'pressureMM', 'атм', makeAreaOptions('pressure')), - ], [ - makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoad', 'т'), - makeDataset('Осевая нагрузка заданная', 'Нагр зад-я','#3D6DCC', 'axialLoadSp', 'т', { dash }), - makeDataset('Осевая нагрузка МАКС', 'Нагр макс','#3D3DCC', 'axialLoadLimitMax', 'т', { dash }), - makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoadMM', 'т', makeAreaOptions('axialLoad')), - ], [ - makeDataset('Вес на крюке', 'Вес на крюке','#00B3B3', 'hookWeight', 'т'), - makeDataset('Вес инструмента ХХ', 'Вес инст ХХ','#29CCB1', 'hookWeightIdle', 'т', { dash }), - makeDataset('Вес инструмента МИН', 'Вес инст мин','#47A1B3', 'hookWeightLimitMin', 'т', { dash }), - makeDataset('Вес инструмента МАКС', 'Вес инст мах','#2D7280', 'hookWeightLimitMax', 'т', { dash }), - makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeed', 'об/мин'), - makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeedMM', 'об/мин', makeAreaOptions('rotorSpeed')), - ], [ - makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorque', 'кН·м'), - makeDataset('План. Момент на роторе', 'Момент зад-й','#9629CC', 'rotorTorqueSp', 'кН·м', { dash }), - makeDataset('Момент на роторе ХХ', 'Момент ХХ','#CC2996', 'rotorTorqueIdle', 'кН·м', { dash }), - makeDataset('Момент МАКС.', 'Момент макс','#FF00FF', 'rotorTorqueLimitMax', 'кН·м', { dash }), - makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorqueMM', 'кН·м', makeAreaOptions('rotorTorque')), - ] - ] -} - -const getLast = (data) => - Array.isArray(data) ? data.at(-1) : data - -const isMseEnabled = (dataSaub) => { - const lastData = getLast(dataSaub) - return (lastData?.mseState && 2) > 0 -} - -const isTorqueStabEnabled = (dataSpin) => { - const lastData = getLast(dataSpin) - return lastData?.state === 7 -} +const getLast = (data) => Array.isArray(data) ? data.at(-1) : data +const isMseEnabled = (dataSaub) => (getLast(dataSaub)?.mseState && 2) > 0 +const isTorqueStabEnabled = (dataSpin) => getLast(dataSpin)?.state === 7 const isSpinEnabled = (dataSpin) => { - const lastData = getLast(dataSpin) - return lastData?.state > 0 && lastData?.state !== 6 + const lastData = getLast(dataSpin) + return lastData?.state > 0 && lastData?.state !== 6 } export const normalizeData = (data) => data?.map(item => ({ - ...item, - rotorSpeed: item.rotorSpeed < 1 ? 0 : item.rotorSpeed, - rotorTorque: item.rotorTorque < 1 ? 0 : item.rotorTorque, - blockSpeed: Math.abs(item.blockSpeed) + ...item, + rotorSpeed: item.rotorSpeed < 1 ? 0 : item.rotorSpeed, + rotorTorque: item.rotorTorque < 1 ? 0 : item.rotorTorque, + blockSpeed: Math.abs(item.blockSpeed) })) ?? [] const dateSorter = makeDateSorter('date') +const makeSubjectSubsription = (subject$, handler) => { + const subscribtion = subject$.pipe( + buffer(subject$.pipe(throttleTime(700))) + ).subscribe((data) => handler(data.flat().filter(Boolean))) + + return () => subscribtion.unsubscribe() +} + const TelemetryView = memo(() => { - const [dataSaub, setDataSaub] = useState([]) - const [dataSpin, setDataSpin] = useState([]) - const [chartInterval, setChartInterval] = useState(defaultPeriod) - const [showLoader, setShowLoader] = useState(false) - const [flowChartData, setFlowChartData] = useState([]) - const [rop, setRop] = useState(null) - const [domain, setDomain] = useState({}) - const [chartMethods, setChartMethods] = useState() + const [dataSaub, setDataSaub] = useState([]) + const [dataSpin, setDataSpin] = useState([]) + const [chartInterval, setChartInterval] = useState(defaultPeriod) + const [showLoader, setShowLoader] = useState(false) + const [flowChartData, setFlowChartData] = useState([]) + const [rop, setRop] = useState(null) + const [domain, setDomain] = useState({}) + const [chartMethods, setChartMethods] = useState() - const [well, updateWell] = useWell() + const [well, updateWell] = useWell() - const saubSubject$ = useMemo(() => new BehaviorSubject(), []) - const spinSubject$ = useMemo(() => new BehaviorSubject(), []) + const saubSubject$ = useMemo(() => new BehaviorSubject(), []) + const spinSubject$ = useMemo(() => new BehaviorSubject(), []) - const handleDataSaub = useCallback((data) => { - if (data) { - const dataSaub = normalizeData(data) - setDataSaub((prev) => { - const out = [...prev, ...dataSaub] - out.sort(dateSorter) - return out - }) - } - }, []) + const handleDataSaub = useCallback((data) => { + if (!data) return - const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), []) + const dataSaub = normalizeData(data) + setDataSaub((prev) => { + const out = [...prev, ...dataSaub] + out.sort(dateSorter) + return out + }) + }, []) - useEffect(() => { - const subscribtion = saubSubject$.pipe( - buffer(saubSubject$.pipe(throttleTime(700))) - ).subscribe((data) => handleDataSaub(data.flat().filter(Boolean))) + const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), []) - return () => subscribtion.unsubscribe() - }, [saubSubject$]) + const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well]) - useEffect(() => { - const subscribtion = spinSubject$.pipe( - buffer(spinSubject$.pipe(throttleTime(700))) - ).subscribe((data) => handleDataSpin(data.flat().filter(Boolean))) + const filteredData = useMemo(() => { + let i, j + for (i = 0; i < dataSaub.length; i++) { + const date = +new Date(dataSaub[i]?.date) + if (date >= +domain.min) break + } - return () => subscribtion.unsubscribe() - }, [spinSubject$]) + for (j = dataSaub.length - 1; j >= i; j--) { + const date = +new Date(dataSaub[i]?.date) + if (date <= +domain.max) break + } - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - const flowChart = await DrillFlowChartService.getByIdWell(well.id) - const dataSaub = await TelemetryDataSaubService.getData(well.id, null, chartInterval) - const dataSpin = await TelemetryDataSpinService.getData(well.id, null, chartInterval) - setFlowChartData(flowChart ?? []) - handleDataSaub(dataSaub) - handleDataSpin(dataSpin) - }, - null, - `Не удалось получить данные`, - { actionName: 'Получение данных по скважине', well } + if (i >= j) return [] + return dataSaub.slice(i, j) + }, [dataSaub, domain]) + + const chartGroups = useMemo(() => makeChartGroups(flowChartData), [flowChartData]) + + useEffect(() => makeSubjectSubsription(saubSubject$, handleDataSaub), [saubSubject$, handleDataSaub]) + useEffect(() => makeSubjectSubsription(spinSubject$, handleDataSpin), [spinSubject$, handleDataSpin]) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const flowChart = await DrillFlowChartService.getByIdWell(well.id) + const dataSaub = await TelemetryDataSaubService.getData(well.id, null, chartInterval) + const dataSpin = await TelemetryDataSpinService.getData(well.id, null, chartInterval) + setFlowChartData(flowChart ?? []) + handleDataSaub(dataSaub) + handleDataSpin(dataSpin) + }, + null, + `Не удалось получить данные`, + { actionName: 'Получение данных по скважине', well } + ) + }, [well, chartInterval, handleDataSpin, handleDataSaub]) + + useEffect(() => { + const unsubscribe = Subscribe( + 'hubs/telemetry', `well_${well.id}`, + { methodName: 'ReceiveDataSaub', handler: (data) => saubSubject$.next(data) }, + { methodName: 'ReceiveDataSpin', handler: (data) => spinSubject$.next(data) } + ) + + return () => unsubscribe() + }, [well.id, saubSubject$, spinSubject$]) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const rop = await OperationStatService.getClusterRopStatByIdWell(well.id) + setRop(rop) + }, + setShowLoader, + `Не удалось загрузить данные`, + { actionName: 'Получение данных по скважине', well } + ) + }, [well]) + + useEffect(() => { + if (dataSaub.length <= 0) return + const last = new Date(dataSaub.at(-1).date) + setDomain({ + min: new Date(+last - chartInterval * 1000), + max: last + }) + }, [dataSaub, chartInterval]) + + return ( + + + +
+ +
+ Интервал:  + +
+ +
+ Статус:  + +
+ +   + +
+ {'TorqueMaster'} + {'SpinMaster'} +

MSE

+
+ +
+
+ + + + + formatDate(d, 'YYYY-MM-DD') + }} + plugins={{ + menu: { enabled: false }, + cursor: { + render: cursorRender, + }, + }} + height={'70vh'} + /> + + + + +
+
) - }, [well, chartInterval, handleDataSpin, handleDataSaub]) - - useEffect(() => { - const unsubscribe = Subscribe( - 'hubs/telemetry', `well_${well.id}`, - { methodName: 'ReceiveDataSaub', handler: (data) => saubSubject$.next(data) }, - { methodName: 'ReceiveDataSpin', handler: (data) => spinSubject$.next(data) } - ) - - return () => unsubscribe() - }, [well.id, saubSubject$, spinSubject$]) - - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - const rop = await OperationStatService.getClusterRopStatByIdWell(well.id) - setRop(rop) - }, - setShowLoader, - `Не удалось загрузить данные`, - { actionName: 'Получение данных по скважине', well } - ) - }, [well]) - - const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well]) - - useEffect(() => { - if (dataSaub.length <= 0) return - const last = new Date(dataSaub.at(-1).date) - setDomain({ - min: new Date(+last - chartInterval * 1000), - max: last - }) - }, [dataSaub, chartInterval]) - - const filteredData = useMemo(() => { - let i, j - for (i = 0; i < dataSaub.length; i++) { - const date = +new Date(dataSaub[i]?.date) - if (date >= +domain.min) break - } - - for (j = dataSaub.length - 1; j >= i; j--) { - const date = +new Date(dataSaub[i]?.date) - if (date <= +domain.max) break - } - - if (i >= j) return [] - return dataSaub.slice(i, j) - }, [dataSaub, domain]) - - const chartGroups = useMemo(() => makeChartGroups(flowChartData), [flowChartData]) - - return ( - - - -
- -
- Интервал:  - -
- -
- Статус:  - -
- -   - -
- {'TorqueMaster'} - {'SpinMaster'} -

MSE

-
- -
-
- - - - - formatDate(d, 'YYYY-MM-DD') - }} - plugins={{ - menu: { enabled: false }, - cursor: { - render: cursorRender, - }, - }} - height={'70vh'} - /> - - - - -
-
- ) }) export default withPermissions(TelemetryView, [ - 'DrillFlowChart.get', - 'OperationStat.get', - 'TelemetryDataSaub.get', - 'TelemetryDataSpin.get', - 'Well.get', + 'DrillFlowChart.get', + 'OperationStat.get', + 'TelemetryDataSaub.get', + 'TelemetryDataSpin.get', + 'Well.get', ]) From 683ccb1c034b080e9f2e72a585bd63104611af3e Mon Sep 17 00:00:00 2001 From: goodmice Date: Sat, 5 Nov 2022 03:20:21 +0500 Subject: [PATCH 05/72] =?UTF-8?q?web-vitals=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D1=91=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 13 +------------ src/react-app-env.d.ts | 1 - src/reportWebVitals.ts | 12 ------------ 3 files changed, 1 insertion(+), 25 deletions(-) delete mode 100755 src/react-app-env.d.ts delete mode 100644 src/reportWebVitals.ts diff --git a/package-lock.json b/package-lock.json index 3cf958f..8912fac 100755 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,7 @@ "react-dom": "^18.1.0", "react-router-dom": "^6.3.0", "rxjs": "^7.5.5", - "usehooks-ts": "^2.6.0", - "web-vitals": "^2.1.4" + "usehooks-ts": "^2.6.0" }, "devDependencies": { "@babel/core": "^7.18.2", @@ -14786,11 +14785,6 @@ "minimalistic-assert": "^1.0.0" } }, - "node_modules/web-vitals": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", - "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==" - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -26492,11 +26486,6 @@ "minimalistic-assert": "^1.0.0" } }, - "web-vitals": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", - "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==" - }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts deleted file mode 100755 index 6431bc5..0000000 --- a/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts deleted file mode 100644 index 30e6a9d..0000000 --- a/src/reportWebVitals.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getCLS, getFID, getFCP, getLCP, getTTFB, ReportHandler } from 'web-vitals' - -export const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (!onPerfEntry) return - getCLS(onPerfEntry) - getFID(onPerfEntry) - getFCP(onPerfEntry) - getLCP(onPerfEntry) - getTTFB(onPerfEntry) -} - -export default reportWebVitals From bf2d6d6d361ee8c73f8c7570b848b9e56b85a115 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 7 Nov 2022 05:38:31 +0500 Subject: [PATCH 06/72] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=81=D0=BA=D0=B2=D0=B0=D0=B6=D0=B8=D0=BD=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Well/Telemetry/TelemetryView/index.jsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index 7a2ca50..b2c654f 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -140,6 +140,7 @@ export const normalizeData = (data) => data?.map(item => ({ const dateSorter = makeDateSorter('date') const TelemetryView = memo(() => { + const [currentWellId, setCurrentWellId] = useState(null) const [dataSaub, setDataSaub] = useState([]) const [dataSpin, setDataSpin] = useState([]) const [chartInterval, setChartInterval] = useState(defaultPeriod) @@ -154,11 +155,11 @@ const TelemetryView = memo(() => { const saubSubject$ = useMemo(() => new BehaviorSubject(), []) const spinSubject$ = useMemo(() => new BehaviorSubject(), []) - const handleDataSaub = useCallback((data) => { + const handleDataSaub = useCallback((data, replace = false) => { if (data) { const dataSaub = normalizeData(data) setDataSaub((prev) => { - const out = [...prev, ...dataSaub] + const out = replace ? [...dataSaub] : [...prev, ...dataSaub] out.sort(dateSorter) return out }) @@ -184,20 +185,22 @@ const TelemetryView = memo(() => { }, [spinSubject$]) useEffect(() => { + if (currentWellId == well.id) return + setCurrentWellId(well.id) invokeWebApiWrapperAsync( async () => { const flowChart = await DrillFlowChartService.getByIdWell(well.id) const dataSaub = await TelemetryDataSaubService.getData(well.id, null, chartInterval) const dataSpin = await TelemetryDataSpinService.getData(well.id, null, chartInterval) setFlowChartData(flowChart ?? []) - handleDataSaub(dataSaub) - handleDataSpin(dataSpin) + handleDataSaub(dataSaub, true) + setDataSpin(Array.isArray(dataSpin) ? dataSpin : []) }, null, `Не удалось получить данные`, { actionName: 'Получение данных по скважине', well } ) - }, [well, chartInterval, handleDataSpin, handleDataSaub]) + }, [well, chartInterval, currentWellId, handleDataSaub]) useEffect(() => { const unsubscribe = Subscribe( From 33b6a2012aadf67d3839e9f907652e16d186b492 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 7 Nov 2022 06:33:14 +0500 Subject: [PATCH 07/72] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=20=D0=BE=D1=87=D0=B8=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Well/Telemetry/TelemetryView/index.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index b2c654f..0587474 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -156,14 +156,14 @@ const TelemetryView = memo(() => { const spinSubject$ = useMemo(() => new BehaviorSubject(), []) const handleDataSaub = useCallback((data, replace = false) => { - if (data) { + setDataSaub((prev) => { + if (!data) + return replace ? [] : prev const dataSaub = normalizeData(data) - setDataSaub((prev) => { - const out = replace ? [...dataSaub] : [...prev, ...dataSaub] - out.sort(dateSorter) - return out - }) - } + const out = replace ? [...dataSaub] : [...prev, ...dataSaub] + out.sort(dateSorter) + return out + }) }, []) const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), []) From 9c85b1e22922fce37dbd35fa7348ae7a95199ce2 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 7 Nov 2022 05:38:31 +0500 Subject: [PATCH 08/72] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=81=D0=BA=D0=B2=D0=B0=D0=B6=D0=B8=D0=BD=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Well/Telemetry/TelemetryView/index.jsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index 35efc86..fd17199 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -140,6 +140,7 @@ export const normalizeData = (data) => data?.map(item => ({ const dateSorter = makeDateSorter('date') const TelemetryView = memo(() => { + const [currentWellId, setCurrentWellId] = useState(null) const [dataSaub, setDataSaub] = useState([]) const [dataSpin, setDataSpin] = useState([]) const [chartInterval, setChartInterval] = useState(defaultPeriod) @@ -155,11 +156,11 @@ const TelemetryView = memo(() => { const saubSubject$ = useMemo(() => new BehaviorSubject(), []) const spinSubject$ = useMemo(() => new BehaviorSubject(), []) - const handleDataSaub = useCallback((data) => { + const handleDataSaub = useCallback((data, replace = false) => { if (data) { const dataSaub = normalizeData(data) setDataSaub((prev) => { - const out = [...prev, ...dataSaub] + const out = replace ? [...dataSaub] : [...prev, ...dataSaub] out.sort(dateSorter) return out }) @@ -185,24 +186,23 @@ const TelemetryView = memo(() => { }, [spinSubject$]) useEffect(() => { - const cancel = invokeWebApiWrapperAsync( - async (signal) => { - if (signal.aborted) return false + if (currentWellId == well.id) return + setCurrentWellId(well.id) + invokeWebApiWrapperAsync( + async () => { const flowChart = await DrillFlowChartService.getByIdWell(well.id) const dataSaub = await TelemetryDataSaubService.getData(well.id, null, chartInterval) const dataSpin = await TelemetryDataSpinService.getData(well.id, null, chartInterval) if (signal.aborted) return false setFlowChartData(flowChart ?? []) - handleDataSaub(dataSaub) - handleDataSpin(dataSpin) + handleDataSaub(dataSaub, true) + setDataSpin(Array.isArray(dataSpin) ? dataSpin : []) }, setIsDataLoading, `Не удалось получить данные`, { actionName: 'Получение данных по скважине', well } ) - - return () => cancel() - }, [well, chartInterval, handleDataSpin, handleDataSaub]) + }, [well, chartInterval, currentWellId, handleDataSaub]) useEffect(() => { const unsubscribe = Subscribe( From f82b80d0d0dbbd93a3c3cfc31356f9cdd12622ea Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 7 Nov 2022 06:33:14 +0500 Subject: [PATCH 09/72] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=20=D0=BE=D1=87=D0=B8=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Well/Telemetry/TelemetryView/index.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index fd17199..4193658 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -157,14 +157,14 @@ const TelemetryView = memo(() => { const spinSubject$ = useMemo(() => new BehaviorSubject(), []) const handleDataSaub = useCallback((data, replace = false) => { - if (data) { + setDataSaub((prev) => { + if (!data) + return replace ? [] : prev const dataSaub = normalizeData(data) - setDataSaub((prev) => { - const out = replace ? [...dataSaub] : [...prev, ...dataSaub] - out.sort(dateSorter) - return out - }) - } + const out = replace ? [...dataSaub] : [...prev, ...dataSaub] + out.sort(dateSorter) + return out + }) }, []) const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), []) From a04090db572a0edb74ad71c7a03c5589efaf1e9a Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 7 Nov 2022 07:12:44 +0500 Subject: [PATCH 10/72] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BD=D0=B0=D0=BB=D0=BE=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82=D1=80=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Well/Telemetry/TelemetryView/index.jsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index 35efc86..e8c90bb 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -140,6 +140,7 @@ export const normalizeData = (data) => data?.map(item => ({ const dateSorter = makeDateSorter('date') const TelemetryView = memo(() => { + const [currentWellId, setCurrentWellId] = useState(null) const [dataSaub, setDataSaub] = useState([]) const [dataSpin, setDataSpin] = useState([]) const [chartInterval, setChartInterval] = useState(defaultPeriod) @@ -155,15 +156,15 @@ const TelemetryView = memo(() => { const saubSubject$ = useMemo(() => new BehaviorSubject(), []) const spinSubject$ = useMemo(() => new BehaviorSubject(), []) - const handleDataSaub = useCallback((data) => { - if (data) { + const handleDataSaub = useCallback((data, replace = false) => { + setDataSaub((prev) => { + if (!data) + return replace ? [] : prev const dataSaub = normalizeData(data) - setDataSaub((prev) => { - const out = [...prev, ...dataSaub] - out.sort(dateSorter) - return out - }) - } + const out = replace ? [...dataSaub] : [...prev, ...dataSaub] + out.sort(dateSorter) + return out + }) }, []) const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), []) @@ -185,24 +186,22 @@ const TelemetryView = memo(() => { }, [spinSubject$]) useEffect(() => { - const cancel = invokeWebApiWrapperAsync( - async (signal) => { - if (signal.aborted) return false + if (currentWellId == well.id) return + setCurrentWellId(well.id) + invokeWebApiWrapperAsync( + async () => { const flowChart = await DrillFlowChartService.getByIdWell(well.id) const dataSaub = await TelemetryDataSaubService.getData(well.id, null, chartInterval) const dataSpin = await TelemetryDataSpinService.getData(well.id, null, chartInterval) - if (signal.aborted) return false setFlowChartData(flowChart ?? []) - handleDataSaub(dataSaub) - handleDataSpin(dataSpin) + handleDataSaub(dataSaub, true) + setDataSpin(Array.isArray(dataSpin) ? dataSpin : []) }, setIsDataLoading, `Не удалось получить данные`, { actionName: 'Получение данных по скважине', well } ) - - return () => cancel() - }, [well, chartInterval, handleDataSpin, handleDataSaub]) + }, [well, chartInterval, currentWellId, handleDataSaub]) useEffect(() => { const unsubscribe = Subscribe( From bc734900290ebfcfb31db528c6eba9d6fdc1f715 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 8 Nov 2022 08:03:05 +0500 Subject: [PATCH 11/72] =?UTF-8?q?=D0=97=D0=B0=D0=B2=D0=B8=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20Table=20=D0=B7=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=B0=20=D1=81=20antd=20=D0=BD=D0=B0?= =?UTF-8?q?=20@components/Table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Cluster/WellOperationsTable.jsx | 3 +-- .../Analytics/WellCompositeEditor/WellCompositeSections.jsx | 4 ++-- src/pages/Well/Reports/DailyReport/ReportEditor.jsx | 6 +++--- .../Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx | 4 ++-- src/pages/Well/WellCase/index.jsx | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/pages/Cluster/WellOperationsTable.jsx b/src/pages/Cluster/WellOperationsTable.jsx index 1d2b094..1e8ee65 100755 --- a/src/pages/Cluster/WellOperationsTable.jsx +++ b/src/pages/Cluster/WellOperationsTable.jsx @@ -1,7 +1,6 @@ import { memo, useMemo } from 'react' -import { Table } from 'antd' -import { makeTextColumn, makeNumericColumnPlanFact } from '@components/Table' +import { Table, makeTextColumn, makeNumericColumnPlanFact } from '@components/Table' import { getPrecision } from '@utils/functions' const columns = [ diff --git a/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx b/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx index 426ed21..52931a4 100644 --- a/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx +++ b/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx @@ -1,12 +1,12 @@ import { Link, useLocation } from 'react-router-dom' import { useState, useEffect, memo, useMemo, lazy, Suspense } from 'react' import { LineChartOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons' -import { Table, Button, Badge, Divider, Modal, Row, Col } from 'antd' +import { Button, Badge, Divider, Modal, Row, Col } from 'antd' import { useWell } from '@asb/context' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' -import { makeTextColumn, makeNumericColumnPlanFact, makeNumericColumn } from '@components/Table' +import { Table, makeTextColumn, makeNumericColumnPlanFact, makeNumericColumn } from '@components/Table' import { WellCompositeService } from '@api' import { hasPermission, diff --git a/src/pages/Well/Reports/DailyReport/ReportEditor.jsx b/src/pages/Well/Reports/DailyReport/ReportEditor.jsx index e77fc61..05c171a 100644 --- a/src/pages/Well/Reports/DailyReport/ReportEditor.jsx +++ b/src/pages/Well/Reports/DailyReport/ReportEditor.jsx @@ -1,15 +1,15 @@ -import { DatePicker, Descriptions, Form, Input, InputNumber, Modal, Table, Tabs } from 'antd' +import { DatePicker, Descriptions, Form, Input, InputNumber, Modal, Table as RawTable, Tabs } from 'antd' import { memo, useCallback, useEffect, useState } from 'react' import moment from 'moment' import { useWell } from '@asb/context' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' -import { makeColumn, makeGroupColumn } from '@components/Table' +import { Table, makeColumn, makeGroupColumn } from '@components/Table' import { DailyReportService } from '@api' const { Item: RawItem } = Form -const { Summary } = Table +const { Summary } = RawTable const { TabPane } = Tabs const Item = memo(({ style, ...other }) => ) diff --git a/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx b/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx index 910aac7..dbe5af1 100644 --- a/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx @@ -1,7 +1,7 @@ -import { Table } from 'antd' import { useState, useEffect, useCallback, memo, useMemo } from 'react' import { useWell } from '@asb/context' +import { Table } from '@components/Table' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' import { Subscribe } from '@services/signalr' @@ -22,7 +22,7 @@ export const ActiveMessagesOnline = memo(({ well: givenWell }) => { if (messages) setMessages(messages.items.splice(0, 4)) }, []) - + const columns = useMemo(() => makeMessageColumns(well.id), [well.id]) useEffect(() => { diff --git a/src/pages/Well/WellCase/index.jsx b/src/pages/Well/WellCase/index.jsx index 3912da6..1eb9709 100644 --- a/src/pages/Well/WellCase/index.jsx +++ b/src/pages/Well/WellCase/index.jsx @@ -1,5 +1,5 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' -import { Alert, Button, Typography } from 'antd' +import { Alert, Button } from 'antd' import { useWell } from '@asb/context' import { UserView } from '@components/views' From aa0fafb7a1e3e9b5a883e39a33dd9ec99fe47cc4 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 8 Nov 2022 08:09:22 +0500 Subject: [PATCH 12/72] =?UTF-8?q?*=20EditableCell=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20=D0=BC=D0=B5=D0=BC=D0=BE=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20*=20=D0=94=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B0=20=D1=81=D0=BE=D1=81=D1=82=D0=B0=D0=B2?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=81=D1=82=D0=BE=D0=BB=D0=B1=D1=86=D0=BE?= =?UTF-8?q?=D0=B2=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=20*=20=D0=A3?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20*=20=D0=9C=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D1=8B-=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D1=8B=20=D1=81=D1=82=D0=BE=D0=BB=D0=B1=D1=86=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D1=8B=20*=20=D0=9C=D0=B5=D1=82=D0=BE=D0=B4=D1=8B-=D0=B3=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80=D1=8B=20=D1=81=D0=BE?= =?UTF-8?q?=D1=80=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=BE=D0=BA=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D1=8B=20=D1=81=20?= =?UTF-8?q?=D1=83=D1=87=D1=91=D1=82=D0=BE=D0=BC=20=D1=81=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=B2=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Table/Columns/date.tsx | 28 ++++-- src/components/Table/Columns/index.ts | 23 +++-- src/components/Table/Columns/numeric.tsx | 73 +++++++++------ src/components/Table/Columns/plan_fact.tsx | 36 +++----- src/components/Table/Columns/select.tsx | 19 ++-- src/components/Table/Columns/tag.tsx | 6 +- src/components/Table/Columns/text.tsx | 19 +++- src/components/Table/Columns/time.tsx | 27 ++++-- src/components/Table/Columns/timezone.tsx | 16 ++-- src/components/Table/EditableCell.tsx | 88 +++++++++++-------- src/components/Table/Table.tsx | 57 ++++++++++-- src/components/Table/TableSettingsChanger.tsx | 12 +-- src/components/Table/index.tsx | 1 - src/components/Table/sorters.ts | 42 --------- .../Well/WellOperations/WellDrillParams.jsx | 73 +-------------- 15 files changed, 251 insertions(+), 269 deletions(-) delete mode 100755 src/components/Table/sorters.ts diff --git a/src/components/Table/Columns/date.tsx b/src/components/Table/Columns/date.tsx index d4790e6..a401a04 100755 --- a/src/components/Table/Columns/date.tsx +++ b/src/components/Table/Columns/date.tsx @@ -1,26 +1,36 @@ -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' -import { formatDate } from '@utils' - -import makeColumn, { columnPropsOther } from '.' -import { DatePickerWrapper, makeDateSorter } from '..' +import { makeColumn, ColumnProps, SorterMethod } from '.' +import { DatePickerWrapper, getObjectByDeepKey } from '..' import { DatePickerWrapperProps } from '../DatePickerWrapper' +import { formatDate, isRawDate } from '@utils' -export const makeDateColumn = ( +export const makeDateSorter = (key: Key): SorterMethod => (a, b) => { + const vA = a ? getObjectByDeepKey(key, a) : null + const vB = b ? getObjectByDeepKey(key, b) : null + + if (!isRawDate(vA) || !isRawDate(vB)) return 0 + if (!isRawDate(vA)) return 1 + if (!isRawDate(vB)) return -1 + + return (new Date(vA)).getTime() - (new Date(vB)).getTime() +} + +export const makeDateColumn = ( title: ReactNode, key: string, utc?: boolean, format?: string, - other?: columnPropsOther, + other?: ColumnProps, pickerOther?: DatePickerWrapperProps, -) => makeColumn(title, key, { +) => makeColumn(title, key, { ...other, render: (date) => (
{formatDate(date, utc, format) ?? '-'}
), - sorter: makeDateSorter(key), + sorter: makeDateSorter(key), input: , }) diff --git a/src/components/Table/Columns/index.ts b/src/components/Table/Columns/index.ts index b8cb6ef..778bffd 100755 --- a/src/components/Table/Columns/index.ts +++ b/src/components/Table/Columns/index.ts @@ -1,6 +1,10 @@ -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' import { Rule } from 'antd/lib/form' -import { ColumnProps } from 'antd/lib/table' +import { ColumnType } from 'antd/lib/table' +import { RenderedCell } from 'rc-table/lib/interface' + +import { DataSet } from '../Table' +import { OmitExtends } from '@utils/types' export * from './date' export * from './time' @@ -12,10 +16,13 @@ export * from './text' export * from './timezone' export type DataType = Record -export type RenderMethod = (value: T, dataset?: DataType, index?: number) => ReactNode -export type SorterMethod = (a?: DataType | null, b?: DataType | null) => number +export type RenderMethod> = (value: T | undefined, dataset: DT, index: number) => ReactNode | RenderedCell | undefined +export type SorterMethod | null | undefined> = (a: DT, b: DT) => number +export type FilterMethod> = (value: string | number | T | undefined, record: DT) => boolean -export type columnPropsOther = ColumnProps> & { +export type FilterGenerator> = (key: Key) => FilterMethod + +export type ColumnProps = OmitExtends<{ // редактируемая колонка editable?: boolean // react компонента редактора @@ -29,10 +36,12 @@ export type columnPropsOther = ColumnProps> & { // дефолтное значение при добавлении новой строки initialValue?: string | number + onFilter?: FilterMethod + sorter?: SorterMethod render?: RenderMethod -} +}, ColumnType>> -export const makeColumn = (title: ReactNode, key: string, other?: columnPropsOther) => ({ +export const makeColumn = (title: ReactNode, key: Key, other?: ColumnProps) => ({ title: title, key: key, dataIndex: key, diff --git a/src/components/Table/Columns/numeric.tsx b/src/components/Table/Columns/numeric.tsx index ad8fdee..36f89b2 100755 --- a/src/components/Table/Columns/numeric.tsx +++ b/src/components/Table/Columns/numeric.tsx @@ -1,20 +1,26 @@ +import { ColumnFilterItem } from 'antd/lib/table/interface' import { InputNumber } from 'antd' -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' -import { makeNumericSorter } from '../sorters' -import makeColumn, { columnPropsOther, DataType, makeGroupColumn, RenderMethod } from '.' -import { ColumnFilterItem, CompareFn } from 'antd/lib/table/interface' +import makeColumn, { ColumnProps, FilterGenerator, makeGroupColumn, RenderMethod, SorterMethod } from '.' +import { getObjectByDeepKey } from '../Table' export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/ -type FilterMethod = (value: string | number | boolean, record: DataType) => boolean +export const makeNumericSorter = (key: Key): SorterMethod => (a, b) => { + if (!a && !b) return 0 + if (!a) return 1 + if (!b) return -1 -export const makeNumericRender = (fixed?: number): RenderMethod => (value: T) => { - let val = '-' - if ((value ?? null) !== null && Number.isFinite(+value)) { + return Number(getObjectByDeepKey(key, a)) - Number(getObjectByDeepKey(key, b)) +} + +export const makeNumericRender = (fixed?: number, defaultValue: string = '-', precision: number = 5): RenderMethod => (value) => { + let val = defaultValue + if (value !== undefined && value !== null && Number.isFinite(+value)) { val = (fixed ?? null) !== null ? (+value).toFixed(fixed) - : (+value).toPrecision(5) + : (+value).toPrecision(precision) } return ( @@ -24,7 +30,7 @@ export const makeNumericRender = (fixed?: number): RenderMeth ) } -export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string): columnPropsOther => ({ +export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string): ColumnProps => ({ editable: true, initialValue: 0, width: 100, @@ -37,14 +43,14 @@ export const makeNumericColumnOptions = (fixed?: number render: makeNumericRender(fixed), }) -export const makeNumericColumn = ( +export const makeNumericColumn = ( title: ReactNode, - key: string, + key: Key, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, width?: string | number, - other?: columnPropsOther, + other?: ColumnProps, ) => makeColumn(title, key, { filters, onFilter: filterDelegate ? filterDelegate(key) : undefined, @@ -56,24 +62,25 @@ export const makeNumericColumn = ( ...other }) -export const makeNumericColumnPlanFact = ( +export const makeNumericColumnPlanFact = ( title: ReactNode, - key: string, + key: Key, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, - width?: string | number + width?: string | number, + other?: ColumnProps, ) => makeGroupColumn(title, [ - makeNumericColumn('п', key + 'Plan', filters, filterDelegate, renderDelegate, width), - makeNumericColumn('ф', key + 'Fact', filters, filterDelegate, renderDelegate, width), + makeNumericColumn('п', key + 'Plan', filters, filterDelegate, renderDelegate, width, other), + makeNumericColumn('ф', key + 'Fact', filters, filterDelegate, renderDelegate, width, other), ]) -export const makeNumericStartEnd = ( +export const makeNumericStartEnd = ( title: ReactNode, - key: string, + key: Key, fixed: number, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, width?: string | number, ) => makeGroupColumn(title, [ @@ -81,12 +88,12 @@ export const makeNumericStartEnd = ( makeNumericColumn('конец', key + 'End', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'End')) ]) -export const makeNumericMinMax = ( +export const makeNumericMinMax = ( title: ReactNode, - key: string, + key: Key, fixed: number, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, width?: string | number, ) => makeGroupColumn(title, [ @@ -94,4 +101,18 @@ export const makeNumericMinMax = ( makeNumericColumn('макс', key + 'Max', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'Max')), ]) +export const makeNumericAvgRange = ( + title: ReactNode, + key: Key, + fixed: number, + filters?: ColumnFilterItem[], + filterDelegate?: FilterGenerator, + renderDelegate?: RenderMethod, + width?: string | number, +) => makeGroupColumn(title, [ + makeNumericColumn('мин', `${key}.min`, filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, `${key}.min`)), + makeNumericColumn('сред', `${key}.avg`, filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, `${key}.avg`)), + makeNumericColumn('макс', `${key}.max`, filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, `${key}.max`)), +]) + export default makeNumericColumn diff --git a/src/components/Table/Columns/plan_fact.tsx b/src/components/Table/Columns/plan_fact.tsx index e890eec..852b9e4 100755 --- a/src/components/Table/Columns/plan_fact.tsx +++ b/src/components/Table/Columns/plan_fact.tsx @@ -1,36 +1,20 @@ -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' -import { columnPropsOther, makeColumn } from '.' +import { ColumnProps, makeColumn } from '.' -export const makeColumnsPlanFact = ( +export const makeColumnsPlanFact = ( title: string | ReactNode, - key: string | string[], - columsOther?: columnPropsOther | [columnPropsOther, columnPropsOther], - gruopOther?: any + key: Key | [Key, Key], + columsOther?: ColumnProps | [ColumnProps, ColumnProps], ) => { - let keyPlanLocal: string - let keyFactLocal: string - - if (key instanceof Array) { - keyPlanLocal = key[0] - keyFactLocal = key[1] - } else { - keyPlanLocal = key + 'Plan' - keyFactLocal = key + 'Fact' - } - - let columsOtherLocal : any[2] - if (columsOther instanceof Array) - columsOtherLocal = [columsOther[0], columsOther[1]] - else - columsOtherLocal = [columsOther, columsOther] + const keys = Array.isArray(key) ? key : [`${key}Plan`, `${key}Fact`] + const others = Array.isArray(columsOther) ? columsOther : [columsOther, columsOther] return { - title: title, - ...gruopOther, + title, children: [ - makeColumn('план', keyPlanLocal, columsOtherLocal[0]), - makeColumn('факт', keyFactLocal, columsOtherLocal[1]), + makeColumn('план', keys[0], others[0]), + makeColumn('факт', keys[1], others[1]), ] } } diff --git a/src/components/Table/Columns/select.tsx b/src/components/Table/Columns/select.tsx index fefc00f..098d983 100755 --- a/src/components/Table/Columns/select.tsx +++ b/src/components/Table/Columns/select.tsx @@ -1,21 +1,22 @@ import { Select, SelectProps } from 'antd' import { DefaultOptionType, SelectValue } from 'antd/lib/select' +import { Key, ReactNode } from 'react' -import { columnPropsOther, makeColumn } from '.' +import { ColumnProps, makeColumn } from '.' -export const makeSelectColumn = ( - title: string, - dataIndex: string, - options: DefaultOptionType[], +export const makeSelectColumn = ( + title: ReactNode, + key: Key, + options: T[], defaultValue?: T, - other?: columnPropsOther, + other?: ColumnProps, selectOther?: SelectProps -) => makeColumn(title, dataIndex, { +) => makeColumn(title, key, { ...other, input: ) }) -export const makeTimezoneColumn = ( +export const makeTimezoneColumn = ( title: ReactNode = 'Зона', - key: string = 'timezone', - defaultValue?: SimpleTimezoneDto, + key: Key = 'timezone', + defaultValue?: T, allowClear: boolean = true, - other?: columnPropsOther, + other?: ColumnProps, selectOther?: TimezoneSelectProps ) => makeColumn(title, key, { width: 100, editable: true, - render: makeTimezoneRenderer(), + render: makeTimezoneRender(), input: ( , HTMLTableDataCellElement> & { - editing?: boolean - dataIndex?: NamePath - input?: ReactNode - isRequired?: boolean - title: string - formItemClass?: string - formItemRules?: Rule[] - children: ReactNode - initialValue: any +export type EditableCellProps = React.DetailedHTMLProps, HTMLTableDataCellElement> & { + editing?: boolean + dataIndex?: Key + input?: ReactNode + isRequired?: boolean + formItemClass?: string + formItemRules?: Rule[] + children: ReactNode + initialValue: any } +const itemStyle = { margin: 0 } + export const EditableCell = memo(({ - editing, - dataIndex, - input, - isRequired, - formItemClass, - formItemRules, - children, - initialValue, - ...other -}) => ( - - {!editing ? children : ( - - {input ?? } - - )} - -)) + editing, + dataIndex, + input, + isRequired, + formItemClass, + formItemRules, + children, + initialValue, + ...other +}) => { + const rules = useMemo(() => formItemRules || [{ + required: isRequired, + message: `Это обязательное поле!`, + }], [formItemRules, isRequired]) + + const name = useMemo(() => dataIndex ? String(dataIndex).split('.') : undefined, [dataIndex]) + const tdStyle = useMemo(() => editing ? { padding: 0 } : undefined, [editing]) + + const edititngItem = useMemo(() => ( + + {input ?? } + + ), [name, rules, formItemClass, initialValue, input]) + + return ( + + {editing ? edititngItem : children} + + ) +}) diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 3b25a0d..c9ecbbe 100755 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,26 +1,65 @@ -import { memo, useCallback, useEffect, useState } from 'react' +import { Key, memo, useCallback, useEffect, useState } from 'react' import { ColumnGroupType, ColumnType } from 'antd/lib/table' import { Table as RawTable, TableProps } from 'antd' +import { RenderMethod } from './Columns' +import { tryAddKeys } from './EditableTable' +import TableSettingsChanger from './TableSettingsChanger' import type { OmitExtends } from '@utils/types' import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils' -import TableSettingsChanger from './TableSettingsChanger' -import { tryAddKeys } from './EditableTable' - import '@styles/index.css' export type BaseTableColumn = ColumnGroupType | ColumnType -export type TableColumns = OmitExtends, TableColumnSettings>[] +export type TableColumn = OmitExtends, TableColumnSettings> +export type TableColumns = TableColumn[] export type TableContainer = TableProps & { - columns: TableColumns + columns: TableColumn[] tableName?: string showSettingsChanger?: boolean } -const _Table = ({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer) => { - const [newColumns, setNewColumns] = useState>([]) +export interface DataSet { + [k: Key]: DataSet | T | D +} + +export const getObjectByDeepKey = (key: Key | undefined, data: DataSet): T | undefined => { + if (!key) return undefined + const parts = String(key).split('.') + let out = data + for (let i = 0; i < parts.length && out; i++) { + const key = parts[i] + if (!(key in out)) return undefined // Если ключ не найдем, считаем значение null + out = out[key] as DataSet // Углубляемся внутрь объекта + } + return out as T +} + +export const makeColumnRenderWrapper = >(key: Key | undefined, render: RenderMethod | undefined): RenderMethod => + (_: any, dataset: T, index: number) => { + const renderFunc: RenderMethod = typeof render === 'function' ? render : (record) => String(record) + return renderFunc(getObjectByDeepKey(key, dataset), dataset, index) + } + + +const applyColumnWrappers = >(columns: BaseTableColumn[]): BaseTableColumn[] => { + return columns.map((column) => { + if ('children' in column) { + return { + ...column, + children: applyColumnWrappers(column.children), + } + } + return { + ...column, + render: makeColumnRenderWrapper(column.key, column.render), + } + }) +} + +function _Table>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer) { + const [newColumns, setNewColumns] = useState[]>([]) const [settings, setSettings] = useState({}) const onSettingsChanged = useCallback((settings?: TableSettings | null) => { @@ -31,7 +70,7 @@ const _Table = ({ columns, dataSource, tableName, showSettings useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName]) useEffect(() => setNewColumns(() => { - const newColumns = applyTableSettings(columns, settings) + const newColumns = applyTableSettings(applyColumnWrappers(columns), settings) if (tableName && showSettingsChanger) { const oldTitle = newColumns[0].title newColumns[0].title = (props) => ( diff --git a/src/components/Table/TableSettingsChanger.tsx b/src/components/Table/TableSettingsChanger.tsx index 4ca622f..2bbb805 100755 --- a/src/components/Table/TableSettingsChanger.tsx +++ b/src/components/Table/TableSettingsChanger.tsx @@ -1,11 +1,11 @@ import { memo, useCallback, useEffect, useState } from 'react' import { ColumnsType } from 'antd/lib/table' -import { Button, Modal, Switch, Table } from 'antd' +import { Button, Modal, Switch } from 'antd' import { SettingOutlined } from '@ant-design/icons' import { TableColumnSettings, makeTableSettings, mergeTableSettings, TableSettings } from '@utils' -import { TableColumns } from './Table' -import { makeColumn } from '.' +import { Table, TableColumns } from './Table' +import { makeColumn, makeTextColumn } from '.' const parseSettings = (columns?: TableColumns, settings?: TableSettings | null): TableColumnSettings[] => { const newSettings = mergeTableSettings(makeTableSettings(columns ?? []), settings ?? {}) @@ -46,8 +46,8 @@ const _TableSettingsChanger = ({ title, columns, settings, onC useEffect(() => { setTableColumns([ - makeColumn('Название', 'title'), - makeColumn(null, 'visible', { + makeTextColumn('Название', 'title'), + makeColumn(null, 'visible', { title: () => ( <> Показать @@ -56,7 +56,7 @@ const _TableSettingsChanger = ({ title, columns, settings, onC ), - render: (visible: boolean, _?: TableColumnSettings, index: number = NaN) => ( + render: (visible, _, index = NaN) => ( (key: keyof DataType): CompareFn> => - (a: DataType, b: DataType) => Number(a[key]) - Number(b[key]) - -export const makeNumericObjSorter = (key: [string, string]) => - (a: DataType, b: DataType) => Number(a[key[0]][key[1]]) - Number(b[key[0]][key[1]]) - -export const makeStringSorter = (key: keyof DataType) => (a?: DataType | null, b?: DataType | null) => { - if (!a && !b) return 0 - if (!a) return 1 - if (!b) return -1 - - return String(a[key]).localeCompare(String(b[key])) -} - -export const makeDateSorter = (key: keyof DataType) => (a: DataType, b: DataType) => { - const adate = a[key] - const bdate = b[key] - if (!isRawDate(adate) || !isRawDate(bdate)) - throw new Error('Date column contains not date formatted string(s)') - - const date = new Date(adate) - - return date.getTime() - new Date(bdate).getTime() -} - -export const makeTimeSorter = (key: keyof DataType) => (a: DataType, b: DataType) => { - const elma = a[key] - const elmb = b[key] - - if (!elma && !elmb) return 0 - if (!elma) return 1 - if (!elmb) return -1 - - return timeToMoment(elma).diff(timeToMoment(elmb)) -} diff --git a/src/pages/Well/WellOperations/WellDrillParams.jsx b/src/pages/Well/WellOperations/WellDrillParams.jsx index 486b422..0ae6097 100644 --- a/src/pages/Well/WellOperations/WellDrillParams.jsx +++ b/src/pages/Well/WellOperations/WellDrillParams.jsx @@ -1,88 +1,17 @@ import { useState, useEffect, useCallback, memo, useMemo } from 'react' -import { InputNumber } from 'antd' import { useWell } from '@asb/context' import { EditableTable, makeSelectColumn, - makeGroupColumn, - makeNumericRender, makeNumericSorter, - RegExpIsFloat, + makeNumericAvgRange, } from '@components/Table' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' import { DrillParamsService, WellOperationService } from '@api' import { arrayOrDefault } from '@utils' -import { makeNumericObjSorter } from '@components/Table/sorters' - -const makeNumericObjRender = (fixed, columnKey) => (value, obj) => { - let val = '-' - const isSelected = obj && columnKey && obj[columnKey[0]] ? obj[columnKey[0]][columnKey[1]] : false - - if ((value ?? null) !== null && Number.isFinite(+value)) { - val = (fixed ?? null) !== null - ? (+value).toFixed(fixed) - : (+value).toPrecision(5) - } - - return ( -
- {val} -
- ) -} - -const makeNumericColumnOptionsWithColor = (fixed, sorterKey, columnKey) => ({ - editable: true, - initialValue: 0, - width: 100, - sorter: sorterKey ? makeNumericObjSorter(sorterKey) : undefined, - formItemRules: [{ - required: true, - message: 'Введите число', - pattern: RegExpIsFloat, - }], - render: makeNumericObjRender(fixed, columnKey), -}) - -const makeNumericObjColumn = ( - title, - dataIndex, - filters, - filterDelegate, - renderDelegate, - width, - other -) => ({ - title: title, - dataIndex: dataIndex, - key: dataIndex, - filters: filters, - onFilter: filterDelegate ? filterDelegate(dataIndex) : null, - sorter: makeNumericObjSorter(dataIndex), - width: width, - input: , - render: renderDelegate ?? makeNumericRender(), - align: 'right', - ...other -}) - -const makeNumericAvgRange = ( - title, - dataIndex, - fixed, - filters, - filterDelegate, - renderDelegate, - width -) => makeGroupColumn(title, [ - makeNumericObjColumn('мин', [dataIndex, 'min'], filters, filterDelegate, renderDelegate, width, makeNumericColumnOptionsWithColor(fixed, [dataIndex, 'min'], [dataIndex, 'isMin'])), - makeNumericObjColumn('сред', [dataIndex, 'avg'], filters, filterDelegate, renderDelegate, width, makeNumericColumnOptionsWithColor(fixed, [dataIndex, 'avg'])), - makeNumericObjColumn('макс', [dataIndex, 'max'], filters, filterDelegate, renderDelegate, width, makeNumericColumnOptionsWithColor(fixed, [dataIndex, 'max'], [dataIndex, 'isMax'])) -]) - export const getColumns = async (idWell) => { let sectionTypes = await WellOperationService.getSectionTypes(idWell) sectionTypes = Object.entries(sectionTypes).map(([id, value]) => ({ From 18f789c980662685f5ec46ab76f4d8043e5fffde Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 10 Nov 2022 21:52:09 +0500 Subject: [PATCH 13/72] =?UTF-8?q?=D0=A1=D1=82=D0=B8=D0=BB=D0=B8=20=D1=81?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=BA=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Grid.tsx | 5 ++--- src/styles/index.css | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/Grid.tsx b/src/components/Grid.tsx index 81783ab..ff38f20 100755 --- a/src/components/Grid.tsx +++ b/src/components/Grid.tsx @@ -22,7 +22,7 @@ export const Grid = memo(({ children, style, ...other }) => ( )) -export const GridItem = memo(({ children, row, col, rowSpan, colSpan, style, ...other }) => { +export const GridItem = memo(({ children, row, col, rowSpan, colSpan, style, className, ...other }) => { const localRow = +row const localCol = +col const localColSpan = colSpan ? colSpan - 1 : 0 @@ -32,12 +32,11 @@ export const GridItem = memo(({ children, row, col, rowSpan, colS gridColumnEnd: localCol + localColSpan, gridRowStart: localRow, gridRowEnd: localRow + localRowSpan, - padding: '4px', ...style, } return ( -
+
{children}
) diff --git a/src/styles/index.css b/src/styles/index.css index 53d4e5d..c2367c8 100755 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -153,4 +153,8 @@ code { .color-pale-green { background-color: #98fb98; +} + +.asb-grid-item { + padding: 4px; } \ No newline at end of file From ac9b4d6c0d9a87912f24f22023fc8b5b567bbf1a Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 10 Nov 2022 23:45:35 +0500 Subject: [PATCH 14/72] =?UTF-8?q?=D0=94=D0=B0=D1=82=D0=B0=D1=81=D0=B5?= =?UTF-8?q?=D1=82=D1=8B=20=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Well/Telemetry/Archive/index.jsx | 5 +- .../Well/Telemetry/TelemetryView/datasets.js | 69 ++++++++++++++++++ .../Well/Telemetry/TelemetryView/index.jsx | 73 +------------------ 3 files changed, 74 insertions(+), 73 deletions(-) create mode 100644 src/pages/Well/Telemetry/TelemetryView/datasets.js diff --git a/src/pages/Well/Telemetry/Archive/index.jsx b/src/pages/Well/Telemetry/Archive/index.jsx index b15afdf..a6a6e6d 100644 --- a/src/pages/Well/Telemetry/Archive/index.jsx +++ b/src/pages/Well/Telemetry/Archive/index.jsx @@ -14,8 +14,9 @@ import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker' import { formatDate, range, withPermissions } from '@utils' import { TelemetryDataSaubService } from '@api' -import { makeChartGroups, normalizeData, yAxis } from '../TelemetryView' -import cursorRender from '../TelemetryView/cursorRender' +import { normalizeData, yAxis } from '../TelemetryView' +import { makeChartGroups } from '../TelemetryView/datasets' +import { cursorRender } from '../TelemetryView/cursorRender' const DATA_COUNT = 2048 // Колличество точек на подгрузку графика const ADDITIVE_PAGES = 2 // Дополнительные данные для графиков diff --git a/src/pages/Well/Telemetry/TelemetryView/datasets.js b/src/pages/Well/Telemetry/TelemetryView/datasets.js new file mode 100644 index 0000000..3c46554 --- /dev/null +++ b/src/pages/Well/Telemetry/TelemetryView/datasets.js @@ -0,0 +1,69 @@ +const dash = [7, 3] + +const makeDataset = (label, shortLabel, color, key, unit, other) => ({ + key, + label, + shortLabel, + color, + xAxis: { + type: 'linear', + accessor: key, + unit, + }, + type: 'line', + ...other, +}) + +export const makeChartGroups = (flowChart) => { + const makeAreaOptions = (accessor) => ({ + type: 'rect_area', + data: flowChart, + hideLabel: true, + yAxis: { + type: 'linear', + accessor: 'depth', + }, + minXAccessor: 'depthStart', + maxXAccessor: 'depthEnd', + minYAccessor: accessor + 'Min', + maxYAccessor: accessor + 'Max', + linkedTo: accessor, + }) + + return [ + [ + makeDataset('Высота блока', 'Высота ТБ','#303030', 'blockPosition', 'м'), + makeDataset('Глубина скважины', 'Глубина скв','#7789A1', 'wellDepth', 'м', { dash }), + makeDataset('Расход', 'Расход','#007070', 'flow', 'л/с'), + makeDataset('Положение долота', 'Долото','#B39D59', 'bitPosition', 'м'), + makeDataset('Расход', 'Расход','#007070', 'flowMM', 'л/с', makeAreaOptions('flow')), + ], [ + makeDataset('Скорость блока', 'Скорость ТБ','#59B359', 'blockSpeed', 'м/ч'), + makeDataset('Скорость заданная', 'Скор зад-я','#95B359', 'blockSpeedSp', 'м/ч', { dash }), + ], [ + makeDataset('Давление', 'Давл','#FF0000', 'pressure', 'атм'), + makeDataset('Давление заданное', 'Давл зад-е','#CC0000', 'pressureSp', 'атм'), + makeDataset('Давление ХХ', 'Давл ХХ','#CC4429', 'pressureIdle', 'атм', { dash }), + makeDataset('Перепад давления МАКС', 'ΔР макс','#B34A36', 'pressureDeltaLimitMax', 'атм', { dash }), + makeDataset('Давление', 'Давл','#FF0000', 'pressureMM', 'атм', makeAreaOptions('pressure')), + ], [ + makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoad', 'т'), + makeDataset('Осевая нагрузка заданная', 'Нагр зад-я','#3D6DCC', 'axialLoadSp', 'т', { dash }), + makeDataset('Осевая нагрузка МАКС', 'Нагр макс','#3D3DCC', 'axialLoadLimitMax', 'т', { dash }), + makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoadMM', 'т', makeAreaOptions('axialLoad')), + ], [ + makeDataset('Вес на крюке', 'Вес на крюке','#00B3B3', 'hookWeight', 'т'), + makeDataset('Вес инструмента ХХ', 'Вес инст ХХ','#29CCB1', 'hookWeightIdle', 'т', { dash }), + makeDataset('Вес инструмента МИН', 'Вес инст мин','#47A1B3', 'hookWeightLimitMin', 'т', { dash }), + makeDataset('Вес инструмента МАКС', 'Вес инст мах','#2D7280', 'hookWeightLimitMax', 'т', { dash }), + makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeed', 'об/мин'), + makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeedMM', 'об/мин', makeAreaOptions('rotorSpeed')), + ], [ + makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorque', 'кН·м'), + makeDataset('План. Момент на роторе', 'Момент зад-й','#9629CC', 'rotorTorqueSp', 'кН·м', { dash }), + makeDataset('Момент на роторе ХХ', 'Момент ХХ','#CC2996', 'rotorTorqueIdle', 'кН·м', { dash }), + makeDataset('Момент МАКС.', 'Момент макс','#FF00FF', 'rotorTorqueLimitMax', 'кН·м', { dash }), + makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorqueMM', 'кН·м', makeAreaOptions('rotorTorque')), + ] + ] +} diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index 0587474..69094ea 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -23,8 +23,9 @@ import WirelineRunOut from './WirelineRunOut' import { CustomColumn } from './CustomColumn' import { ModeDisplay } from './ModeDisplay' import { UserOfWell } from './UserOfWells' -import cursorRender from './cursorRender' import { Setpoints } from './Setpoints' +import { makeChartGroups } from './datasets' +import { cursorRender } from './cursorRender' import MomentStabPicEnabled from '@images/DempherOn.png' import MomentStabPicDisabled from '@images/DempherOff.png' @@ -42,76 +43,6 @@ export const yAxis = { format: (d) => formatDate(d, undefined, 'DD.MM.YYYY HH:mm:ss'), } -const dash = [7, 3] - -const makeDataset = (label, shortLabel, color, key, unit, other) => ({ - key, - label, - shortLabel, - color, - xAxis: { - type: 'linear', - accessor: key, - unit, - }, - type: 'line', - ...other, -}) - -export const makeChartGroups = (flowChart) => { - const makeAreaOptions = (accessor) => ({ - type: 'rect_area', - data: flowChart, - hideLabel: true, - yAxis: { - type: 'linear', - accessor: 'depth', - }, - minXAccessor: 'depthStart', - maxXAccessor: 'depthEnd', - minYAccessor: accessor + 'Min', - maxYAccessor: accessor + 'Max', - linkedTo: accessor, - }) - - return [ - [ - makeDataset('Высота блока', 'Высота ТБ','#303030', 'blockPosition', 'м'), - makeDataset('Глубина скважины', 'Глубина скв','#7789A1', 'wellDepth', 'м', { dash }), - makeDataset('Расход', 'Расход','#007070', 'flow', 'л/с'), - makeDataset('Положение долота', 'Долото','#B39D59', 'bitPosition', 'м'), - makeDataset('Расход', 'Расход','#007070', 'flowMM', 'л/с', makeAreaOptions('flow')), - ], [ - makeDataset('Скорость блока', 'Скорость ТБ','#59B359', 'blockSpeed', 'м/ч'), - makeDataset('Скорость заданная', 'Скор зад-я','#95B359', 'blockSpeedSp', 'м/ч', { dash }), - ], [ - makeDataset('Давление', 'Давл','#FF0000', 'pressure', 'атм'), - makeDataset('Давление заданное', 'Давл зад-е','#CC0000', 'pressureSp', 'атм'), - makeDataset('Давление ХХ', 'Давл ХХ','#CC4429', 'pressureIdle', 'атм', { dash }), - makeDataset('Перепад давления МАКС', 'ΔР макс','#B34A36', 'pressureDeltaLimitMax', 'атм', { dash }), - makeDataset('Давление', 'Давл','#FF0000', 'pressureMM', 'атм', makeAreaOptions('pressure')), - ], [ - makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoad', 'т'), - makeDataset('Осевая нагрузка заданная', 'Нагр зад-я','#3D6DCC', 'axialLoadSp', 'т', { dash }), - makeDataset('Осевая нагрузка МАКС', 'Нагр макс','#3D3DCC', 'axialLoadLimitMax', 'т', { dash }), - makeDataset('Осевая нагрузка', 'Нагр','#0000CC', 'axialLoadMM', 'т', makeAreaOptions('axialLoad')), - ], [ - makeDataset('Вес на крюке', 'Вес на крюке','#00B3B3', 'hookWeight', 'т'), - makeDataset('Вес инструмента ХХ', 'Вес инст ХХ','#29CCB1', 'hookWeightIdle', 'т', { dash }), - makeDataset('Вес инструмента МИН', 'Вес инст мин','#47A1B3', 'hookWeightLimitMin', 'т', { dash }), - makeDataset('Вес инструмента МАКС', 'Вес инст мах','#2D7280', 'hookWeightLimitMax', 'т', { dash }), - makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeed', 'об/мин'), - makeDataset('Обороты ротора', 'Об ротора','#11B32F', 'rotorSpeedMM', 'об/мин', makeAreaOptions('rotorSpeed')), - ], [ - makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorque', 'кН·м'), - makeDataset('План. Момент на роторе', 'Момент зад-й','#9629CC', 'rotorTorqueSp', 'кН·м', { dash }), - makeDataset('Момент на роторе ХХ', 'Момент ХХ','#CC2996', 'rotorTorqueIdle', 'кН·м', { dash }), - makeDataset('Момент МАКС.', 'Момент макс','#FF00FF', 'rotorTorqueLimitMax', 'кН·м', { dash }), - makeDataset('Момент на роторе', 'Момент','#990099', 'rotorTorqueMM', 'кН·м', makeAreaOptions('rotorTorque')), - ] - ] -} - const getLast = (data) => Array.isArray(data) ? data.at(-1) : data From cc1c6a0661efc1a8e61cf9e2472223c428018f46 Mon Sep 17 00:00:00 2001 From: goodmice Date: Fri, 11 Nov 2022 00:06:11 +0500 Subject: [PATCH 15/72] =?UTF-8?q?*=20=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=B6=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=82=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B2=20*=20=D0=9E=D0=B1=D1=91=D1=80=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=86=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=B2=20=D0=BB=D0=B5=D0=BD=D0=B8=D0=B2=D1=83=D1=8E=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D1=83=20*=20=D0=A1?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=20=D0=BC=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=B8=20=D1=81=D0=B5=D0=BB=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D1=8B?= =?UTF-8?q?=20=D1=81=D0=BA=D0=B2=D0=B0=D0=B6=D0=B8=D0=BD=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D1=8B=20=D1=81=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D1=82=D0=B0=20*=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BC=D0=B5=D0=BC=D0=BE=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=87=D1=91=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 22 ++- src/components/outlets/DepositsOutlet.tsx | 35 ++++ src/components/outlets/index.ts | 1 + src/components/selectors/WellSelector.jsx | 11 +- src/components/selectors/WellTreeSelector.tsx | 118 ++++++------ src/context.ts | 32 +++- src/pages/Deposit.jsx | 176 ++++++++---------- src/styles/index.css | 13 +- 8 files changed, 223 insertions(+), 185 deletions(-) create mode 100644 src/components/outlets/DepositsOutlet.tsx diff --git a/src/App.tsx b/src/App.tsx index 0ac3eb0..28ed1b2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,8 +4,6 @@ import locale from 'antd/lib/locale/ru_RU' import { ConfigProvider } from 'antd' import { RootPathContext } from '@asb/context' -import { UserOutlet } from '@components/outlets' -import LayoutPortal from '@components/LayoutPortal' import SuspenseFallback from '@components/SuspenseFallback' import { getUser, NoAccessComponent } from '@utils' import { OpenAPI } from '@api' @@ -13,6 +11,10 @@ import { OpenAPI } from '@api' import '@styles/include/antd_theme.less' import '@styles/App.less' +const UserOutlet = lazy(() => import('@components/outlets/UserOutlet')) +const DepositsOutlet = lazy(() => import('@components/outlets/DepositsOutlet')) +const LayoutPortal = lazy(() => import('@components/LayoutPortal')) + const Login = lazy(() => import('@pages/public/Login')) const Register = lazy(() => import('@pages/public/Register')) const FileDownload = lazy(() => import('@pages/FileDownload')) @@ -44,14 +46,16 @@ export const App = memo(() => ( }> } /> - }> - {/* Admin pages */} - } /> + }> + }> + {/* Admin pages */} + } /> - {/* Client pages */} - } /> - } /> - } /> + {/* Client pages */} + } /> + } /> + } /> + diff --git a/src/components/outlets/DepositsOutlet.tsx b/src/components/outlets/DepositsOutlet.tsx new file mode 100644 index 0000000..d191222 --- /dev/null +++ b/src/components/outlets/DepositsOutlet.tsx @@ -0,0 +1,35 @@ +import { memo, useEffect, useState } from 'react' +import { Outlet } from 'react-router-dom' + +import { DepositsContext } from '@asb/context' +import LoaderPortal from '@components/LoaderPortal' +import { invokeWebApiWrapperAsync } from '@components/factory' +import { DepositDto, DepositService } from '@api' +import { arrayOrDefault } from '@utils' + +export const DepositsOutlet = memo(() => { + const [deposits, setDeposits] = useState([]) + const [isLoading, setIsLoading] = useState(false) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const deposits = await DepositService.getDeposits() + setDeposits(arrayOrDefault(deposits)) + }, + setIsLoading, + `Не удалось загрузить список кустов`, + { actionName: 'Получить список кустов' } + ) + }, []) + + return ( + + + + + + ) +}) + +export default DepositsOutlet diff --git a/src/components/outlets/index.ts b/src/components/outlets/index.ts index 77c9a70..3542fbb 100644 --- a/src/components/outlets/index.ts +++ b/src/components/outlets/index.ts @@ -1 +1,2 @@ +export * from './DepositsOutlet' export * from './UserOutlet' diff --git a/src/components/selectors/WellSelector.jsx b/src/components/selectors/WellSelector.jsx index ba448be..da7d95c 100755 --- a/src/components/selectors/WellSelector.jsx +++ b/src/components/selectors/WellSelector.jsx @@ -1,12 +1,11 @@ import { Tag, TreeSelect } from 'antd' import { memo, useEffect, useState } from 'react' +import { useDeposits } from '@asb/context' import { invokeWebApiWrapperAsync } from '@components/factory' import { hasPermission } from '@utils' -import { DepositService } from '@api' -export const getTreeData = async () => { - const deposits = await DepositService.getDeposits() +export const getTreeData = async (deposits) => { const wellsTree = deposits.map((deposit, dIdx) => ({ title: deposit.caption, key: `0-${dIdx}`, @@ -40,10 +39,12 @@ export const WellSelector = memo(({ value, onChange, treeData, treeLabels, ...ot const [wellsTree, setWellsTree] = useState([]) const [wellLabels, setWellLabels] = useState([]) + const deposits = useDeposits() + useEffect(() => { invokeWebApiWrapperAsync( async () => { - const wellsTree = treeData ?? await getTreeData() + const wellsTree = treeData ?? await getTreeData(deposits) const labels = treeLabels ?? getTreeLabels(wellsTree) setWellsTree(wellsTree) setWellLabels(labels) @@ -52,7 +53,7 @@ export const WellSelector = memo(({ value, onChange, treeData, treeLabels, ...ot 'Не удалось загрузить список скважин', { actionName: 'Получение списка скважин' } ) - }, [treeData, treeLabels]) + }, [deposits, treeData, treeLabels]) return ( deposits.map(deposit =>({ + title: deposit.caption, + key: `/deposit/${deposit.id}`, + value: `/deposit/${deposit.id}`, + icon: , + 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: , + children: wells.map(well => ({ + title: well.caption, + key: `/well/${well.id}`, + value: `/well/${well.id}`, + icon: + })), + } + }), +})) + export const WellTreeSelector = memo(({ expand, current, onChange, onClose, open, ...other }) => { - const [wellsTree, setWellsTree] = useState([]) - const [showLoader, setShowLoader] = useState(false) const [expanded, setExpanded] = useState([]) const [selected, setSelected] = useState([]) const navigate = useNavigate() const location = useLocation() + const deposits = useDeposits() - useEffect(() => { - if (current) setSelected([current]) - }, [current]) - - useEffect(() => { - setExpanded((prev) => expand ? getExpandKeys(wellsTree, expand) : prev) - }, [wellsTree, expand]) - - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - const deposits: Array = await DepositService.getDeposits() - const wellsTree: TreeDataNode[] = deposits.map(deposit =>({ - title: deposit.caption, - key: `/deposit/${deposit.id}`, - value: `/deposit/${deposit.id}`, - icon: , - 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: , - children: wells.map(well => ({ - title: well.caption, - key: `/well/${well.id}`, - value: `/well/${well.id}`, - icon: - })), - } - }), - })) - setWellsTree(wellsTree) - }, - setShowLoader, - `Не удалось загрузить список скважин`, - { actionName: 'Получить список скважин' } - ) - }, []) + const wellsTree = useMemo(() => makeWellsTreeData(deposits), [deposits]) const onValueChange = useCallback((value?: string): void => { const key = getKeyByUrl(value)[0] @@ -169,21 +151,27 @@ export const WellTreeSelector = memo(({ expand, current, navigate(newPath, { state: { from: location.pathname }}) }, [navigate, location]) - useEffect(() => onValueChange(location.pathname), [onValueChange, 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 ( - - - + ) }) diff --git a/src/context.ts b/src/context.ts index bd186a5..d719031 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,7 +1,7 @@ import { createContext, useContext, useEffect } from 'react' import { LayoutPortalProps } from '@components/LayoutPortal' -import { UserTokenDto, WellDto } from '@api' +import { DepositDto, UserTokenDto, WellDto } from '@api' /** Контекст текущей скважины */ export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}]) @@ -13,6 +13,8 @@ export const UserContext = createContext({}) export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {}) /** Контекст для блока справа от крошек на страницах скважин и админки */ export const TopRightBlockContext = createContext<(block: JSX.Element) => void>(() => {}) +/** Контекст со списком месторождений */ +export const DepositsContext = createContext([]) /** * Получить текущую скважину @@ -29,19 +31,31 @@ export const useWell = () => useContext(WellContext) export const useRootPath = () => useContext(RootPathContext) /** - * Получить текущего пользователя - * - * @returns Текущий пользователь, либо `null` - */ + * Получить текущего пользователя + * + * @returns Текущий пользователь, либо `null` + */ export const useUser = () => useContext(UserContext) +/** + * Получить список скважин + * + * @returns Список скважин + */ +export const useDeposits = () => useContext(DepositsContext) + +/** + * Получить метод задания элементов справа от крошек + * + * @returns Метод задания элементов справа от крошек + */ export const useTopRightBlock = () => useContext(TopRightBlockContext) /** - * Получить метод задания параметров заголовка и меню - * - * @returns Получить метод задания параметров заголовка и меню - */ + * Получить метод задания параметров заголовка и меню + * + * @returns Получить метод задания параметров заголовка и меню + */ export const useLayoutProps = (props?: LayoutPortalProps) => { const setLayoutProps = useContext(LayoutPropsContext) diff --git a/src/pages/Deposit.jsx b/src/pages/Deposit.jsx index 9779e78..f0eb9c3 100755 --- a/src/pages/Deposit.jsx +++ b/src/pages/Deposit.jsx @@ -1,120 +1,106 @@ -import { useState, useEffect, memo, useMemo } from 'react' +import { useEffect, memo, useMemo, useCallback } from 'react' import { Link, useLocation } from 'react-router-dom' import { Map, Overlay } from 'pigeon-maps' import { Popover, Badge } from 'antd' -import { useLayoutProps } from '@asb/context' +import { useDeposits, useLayoutProps } from '@asb/context' import { PointerIcon } from '@components/icons' -import LoaderPortal from '@components/LoaderPortal' import { FastRunMenu } from '@components/FastRunMenu' -import { invokeWebApiWrapperAsync } from '@components/factory' -import { arrayOrDefault, limitValue, withPermissions } from '@utils' -import { DepositService } from '@api' +import { limitValue, withPermissions } from '@utils' import '@styles/index.css' -const defaultViewParams = { center: [60.81226, 70.0562], zoom: 5 } - const zoomLimit = limitValue(5, 15) const calcViewParams = (clusters) => { - if ((clusters?.length ?? 0) <= 0) - return defaultViewParams + if ((clusters?.length ?? 0) <= 0) + return { center: [60.81226, 70.0562], zoom: 5 } - const center = clusters.reduce((sum, cluster) => { - sum[0] += (cluster.latitude / clusters.length) - sum[1] += (cluster.longitude / clusters.length) - return sum - }, [0, 0]) + const center = clusters.reduce((sum, cluster) => { + sum[0] += cluster.latitude + sum[1] += cluster.longitude + return sum + }, [0, 0]).map((elm) => elm / clusters.length) - const maxDeg = clusters.reduce((max, cluster) => { - const dLatitude = Math.abs(center[0] - cluster.latitude) - const dLongitude = Math.abs(center[1] - cluster.longitude) - const d = dLatitude > dLongitude ? dLatitude : dLongitude - return d > max ? d : max - }, 0) + const maxDeg = clusters.reduce((max, cluster) => { + const dLatitude = Math.abs(center[0] - cluster.latitude) + const dLongitude = Math.abs(center[1] - cluster.longitude) + return Math.max(Math.max(dLatitude, dLongitude), max) + }, 0) - // zoom max = 20 (too close) - // zoom min = 1 (mega far) - // 4 - full Russia (161.6 deg) - // 13.5 - Khanty-Mansiysk - const zoom = zoomLimit(5 + 5 / (maxDeg + 0.5)) + // zoom max = 20 (too close) + // zoom min = 1 (mega far) + // 4 - full Russia (161.6 deg) + // 13.5 - Khanty-Mansiysk + const zoom = zoomLimit(5 + 5 / (maxDeg + 0.5)) - return { center, zoom } + return { center, zoom } } const Deposit = memo(() => { - const [depositsData, setDepositsData] = useState([]) - const [showLoader, setShowLoader] = useState(false) - const [viewParams, setViewParams] = useState(defaultViewParams) + const deposits = useDeposits() + const setLayoutProps = useLayoutProps() + const location = useLocation() - const setLayoutProps = useLayoutProps() - - const location = useLocation() - - const selectorProps = useMemo(() => { - const hasId = location.pathname.length > '/deposit/'.length - - return { - expand: hasId ? [location.pathname] : true, - current: hasId ? location.pathname : undefined, - } - }, [location.pathname]) - - useEffect(() => setLayoutProps({ - sheet: false, - showSelector: true, - selectorProps, - title: 'Месторождение', - }), [setLayoutProps, selectorProps]) - - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - const deposits = await DepositService.getDeposits() - setDepositsData(arrayOrDefault(deposits)) - setViewParams(calcViewParams(deposits)) - }, - setShowLoader, - `Не удалось загрузить список кустов`, - { actionName: 'Получить список кустов' } - ) - }, []) - - return ( - <> - - -
- - {depositsData.map(deposit => ( - - - {deposit.clusters.map(cluster => ( - -
{cluster.caption}
- - ))} -
- } trigger={['click']} title={deposit.caption}> -
- - - -
- - + const makeDepositLinks = useCallback((clusters) => ( +
+ {clusters.map(cluster => ( + +
{cluster.caption}
+ ))} -
-
- - ) + ), [location.pathname]) + + const viewParams = useMemo(() => calcViewParams(deposits), [deposits]) + + useEffect(() => { + const hasId = location.pathname.length > '/deposit/'.length + + const selectorProps = { + expand: hasId ? [location.pathname] : true, + current: hasId ? location.pathname : undefined, + } + + setLayoutProps({ + sheet: false, + showSelector: true, + selectorProps, + title: 'Месторождение', + }) + }, [setLayoutProps, location.pathname]) + + return ( + <> + +
+ + {deposits.map(deposit => { + const anchor = [deposit.latitude, deposit.longitude] + const links = makeDepositLinks(deposit.clusters) + + return ( + + +
+ + + +
+
+
+ ) + })} +
+
+ + ) }) export default withPermissions(Deposit, ['Cluster.get']) diff --git a/src/styles/index.css b/src/styles/index.css index c2367c8..217ba16 100755 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -135,7 +135,7 @@ code { -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 */ + supported by Chrome, Edge, Opera and Firefox */ } .download-link { @@ -157,4 +157,13 @@ code { .asb-grid-item { padding: 4px; -} \ No newline at end of file +} + +.pointer { + cursor: pointer; +} + +.deposit-page { + height: 100vh; + overflow: hidden; +} From b2d241a8e7c0a76d9d2a9768ea2c755a877a04ee Mon Sep 17 00:00:00 2001 From: goodmice Date: Sun, 13 Nov 2022 14:25:03 +0500 Subject: [PATCH 16/72] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=20=D0=BC=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8C=D0=BA=D0=B8=D1=85=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 63 ++++++++---------- src/components/LoaderPortal.tsx | 5 +- src/components/Table/EditableTable.jsx | 4 +- .../d3/monitoring/D3MonitoringCharts.tsx | 10 +-- src/index.tsx | 17 ++++- src/pages/Cluster/ClusterWells.jsx | 7 +- src/pages/Well/Telemetry/Archive/index.jsx | 3 +- src/pages/Well/Telemetry/Messages.jsx | 4 +- .../Well/Telemetry/TelemetryView/index.jsx | 4 +- src/styles/include/antd_theme.less | 7 +- src/styles/layout.less | 25 +++++-- src/styles/loader.css | 7 ++ src/styles/monitoring.less | 17 ----- src/styles/telemetry_view.less | 66 +++++++++++++++++++ 14 files changed, 156 insertions(+), 83 deletions(-) delete mode 100644 src/styles/monitoring.less create mode 100644 src/styles/telemetry_view.less diff --git a/src/App.tsx b/src/App.tsx index 28ed1b2..0d84e96 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,10 @@ import { BrowserRouter as Router, Navigate, Route, Routes } from 'react-router-dom' import { lazy, memo, Suspense } from 'react' -import locale from 'antd/lib/locale/ru_RU' -import { ConfigProvider } from 'antd' import { RootPathContext } from '@asb/context' import SuspenseFallback from '@components/SuspenseFallback' -import { getUser, NoAccessComponent } from '@utils' -import { OpenAPI } from '@api' +import { NoAccessComponent } from '@utils' -import '@styles/include/antd_theme.less' import '@styles/App.less' const UserOutlet = lazy(() => import('@components/outlets/UserOutlet')) @@ -24,45 +20,38 @@ const Deposit = lazy(() => import('@pages/Deposit')) const Cluster = lazy(() => import('@pages/Cluster')) const Well = lazy(() => import('@pages/Well')) -// OpenAPI.BASE = 'http://localhost:3000' -// TODO: Удалить взятие из 'token' в следующем релизе, вставлено для совместимости -OpenAPI.TOKEN = async () => getUser().token || localStorage.getItem('token') || '' -OpenAPI.HEADERS = { 'Content-Type': 'application/json' } - export const App = memo(() => ( - - - }> - - - } /> - } /> + + }> + + + } /> + } /> - {/* Public pages */} - } /> - } /> + {/* Public pages */} + } /> + } /> - {/* User pages */} - }> - } /> + {/* User pages */} + }> + } /> - }> - }> - {/* Admin pages */} - } /> + }> + }> + {/* Admin pages */} + } /> - {/* Client pages */} - } /> - } /> - } /> - + {/* Client pages */} + } /> + } /> + } /> - - - - - +
+ + + + )) export default App diff --git a/src/components/LoaderPortal.tsx b/src/components/LoaderPortal.tsx index 04d9999..770c37a 100755 --- a/src/components/LoaderPortal.tsx +++ b/src/components/LoaderPortal.tsx @@ -6,11 +6,12 @@ type LoaderPortalProps = HTMLAttributes & { show?: boolean, fade?: boolean, spinnerProps?: HTMLAttributes, + fillContent?: boolean } -export const LoaderPortal: React.FC = ({ className = '', show, fade = true, children, spinnerProps, ...other }) => ( +export const LoaderPortal: React.FC = ({ className = '', show, fade = true, children, spinnerProps, fillContent, ...other }) => (
-
{children}
+
{children}
{show && fade &&
} {show &&
}
diff --git a/src/components/Table/EditableTable.jsx b/src/components/Table/EditableTable.jsx index a94955e..a471789 100755 --- a/src/components/Table/EditableTable.jsx +++ b/src/components/Table/EditableTable.jsx @@ -221,9 +221,7 @@ export const EditableTable = memo(({ const mergedColumns = useMemo(() => [...columns.map(handleColumn), operationColumn], [columns, handleColumn, operationColumn]) - useEffect(() => { - setData(tryAddKeys(dataSource)) - }, [dataSource]) + useEffect(() => setData(tryAddKeys(dataSource)), [dataSource]) return (
diff --git a/src/components/d3/monitoring/D3MonitoringCharts.tsx b/src/components/d3/monitoring/D3MonitoringCharts.tsx index dbc2a54..bfeb30b 100644 --- a/src/components/d3/monitoring/D3MonitoringCharts.tsx +++ b/src/components/d3/monitoring/D3MonitoringCharts.tsx @@ -193,6 +193,7 @@ const _D3MonitoringCharts = >({ methods, className = '', + style, ...other }: D3MonitoringChartsProps) => { const [datasets, setDatasets, resetDatasets] = useUserSettings(chartName, datasetGroups) @@ -351,10 +352,10 @@ const _D3MonitoringCharts = >({ x: getByAccessor(dataset.xAxis?.accessor), } ) - + if (newChart.type === 'line') newChart.optimization = false - + // Если у графика нет группы создаём её if (newChart().empty()) group().append('g') @@ -496,12 +497,12 @@ const _D3MonitoringCharts = >({ default: break } - + if (chart.point) renderPoint(xAxis, yAxis, chart, chartData, true) if (dash) chart().attr('stroke-dasharray', dash) - + chart.afterDraw?.(chart) }) }) @@ -513,6 +514,7 @@ const _D3MonitoringCharts = >({ style={{ width: givenWidth, height: givenHeight, + ...style, }} >
getUser().token || localStorage.getItem('token') || '' +OpenAPI.HEADERS = { 'Content-Type': 'application/json' } + const container = document.getElementById('root') ?? document.body const root = createRoot(container) root.render( - + + + ) diff --git a/src/pages/Cluster/ClusterWells.jsx b/src/pages/Cluster/ClusterWells.jsx index a74c8bd..a84e9cb 100755 --- a/src/pages/Cluster/ClusterWells.jsx +++ b/src/pages/Cluster/ClusterWells.jsx @@ -7,11 +7,11 @@ import { makeTextColumn, makeGroupColumn, makeColumn, - makeDateSorter, makeNumericColumnPlanFact, Table, makeNumericRender, makeNumericColumn, + makeDateColumn, } from '@components/Table' import LoaderPortal from '@components/LoaderPortal' import PointerIcon from '@components/icons/PointerIcon' @@ -39,7 +39,6 @@ const filtersWellsType = [] const DAY_IN_MS = 86_400_000 const ONLINE_DEADTIME = 600_000 -const getDate = (str) => isRawDate(str) ? new Date(str).toLocaleString() : '-' const numericRender = makeNumericRender(1) const ClusterWells = memo(({ statsWells }) => { @@ -131,8 +130,8 @@ const ClusterWells = memo(({ statsWells }) => { ), makeTextColumn('Тип скв.', 'wellType', filtersWellsType, null, (text) => text ?? '-'), makeGroupColumn('Фактические сроки', [ - makeColumn('начало', 'factStart', { sorter: makeDateSorter('factStart'), render: getDate }), - makeColumn('окончание', 'factEnd', { sorter: makeDateSorter('factEnd'), render: getDate }), + makeDateColumn('начало', 'factStart'), + makeDateColumn('окончание', 'factEnd'), ]), makeNumericColumnPlanFact('Продолжительность, сут', 'period', filtersMinMax, makeFilterMinMaxFunction, numericRender), makeNumericColumnPlanFact('МСП, м/ч', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction, numericRender), diff --git a/src/pages/Well/Telemetry/Archive/index.jsx b/src/pages/Well/Telemetry/Archive/index.jsx index a6a6e6d..c708eaf 100644 --- a/src/pages/Well/Telemetry/Archive/index.jsx +++ b/src/pages/Well/Telemetry/Archive/index.jsx @@ -223,7 +223,7 @@ const Archive = memo(() => { const chartData = useMemo(() => cutData(dataSaub, domain.min, domain.max), [dataSaub, domain]) return ( - +
Начальная дата:  @@ -259,6 +259,7 @@ const Archive = memo(() => { render: cursorRender, } }} + style={{ flexGrow: 1 }} height={'76vh'} onWheel={onGraphWheel} /> diff --git a/src/pages/Well/Telemetry/Messages.jsx b/src/pages/Well/Telemetry/Messages.jsx index 653e122..4af1a31 100644 --- a/src/pages/Well/Telemetry/Messages.jsx +++ b/src/pages/Well/Telemetry/Messages.jsx @@ -28,7 +28,7 @@ const categoryDictionary = { // Конфигурация таблицы export const makeMessageColumns = (idWell) => [ - makeDateColumn('Дата', 'date', undefined, undefined, { width: '10rem' }), + makeDateColumn('Дата', 'date', undefined, undefined, { width: '120px' }), makeNumericColumn('Глубина, м', 'wellDepth', null, null, (depth, item) => ( [ ellipsis: true, }), makeColumn('Сообщение', 'message', { onFilter: (value, record) => record.name.indexOf(value) === 0 }), - makeTextColumn('Пользователь', 'user', null, null, null, { width: '10rem' }), + makeTextColumn('Пользователь', 'user', null, null, null, { width: '120px' }), ] const filterOptions = [ diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index 69094ea..a2fa8f1 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -32,7 +32,7 @@ import MomentStabPicDisabled from '@images/DempherOff.png' import SpinPicEnabled from '@images/SpinEnabled.png' import SpinPicDisabled from '@images/SpinDisabled.png' -import '@styles/monitoring.less' +import '@styles/telemetry_view.less' import '@styles/message.less' const { Option } = Select @@ -186,7 +186,7 @@ const TelemetryView = memo(() => { return ( - +
diff --git a/src/styles/include/antd_theme.less b/src/styles/include/antd_theme.less index 68e0e3f..c9acd72 100644 --- a/src/styles/include/antd_theme.less +++ b/src/styles/include/antd_theme.less @@ -7,8 +7,5 @@ // Переменные для темы тут: // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less -//@primary-color: rgba(124, 124, 124, 0.3); -@primary-color: rgb(195, 40,40); -//@primary-color:rgb(65, 63, 61); -//@layout-header-background: rgb(195, 40,40); -@layout-header-background: rgb(65, 63, 61); +@primary-color: #C32828; // rgb(195, 40, 40) +@layout-header-background:#413F3D; // rgb(65, 63, 61) diff --git a/src/styles/layout.less b/src/styles/layout.less index 4d5861f..225dd03 100644 --- a/src/styles/layout.less +++ b/src/styles/layout.less @@ -19,6 +19,10 @@ } .page-layout { + --sheet-padding: 15px; + + @sheet-padding: var(--sheet-padding); + height: 100vh; & .menu-sider { @@ -62,7 +66,7 @@ background-color: transparent; cursor: pointer; overflow: hidden; - + &:hover { color: white; } @@ -88,8 +92,9 @@ display: flex; justify-content: space-between; align-items: center; - gap: 15px; - padding: 0 15px 15px 15px; + gap: @sheet-padding; + padding: @sheet-padding; + padding-top: 0; & .ant-breadcrumb-link, .ant-breadcrumb-separator { .no-select; @@ -99,7 +104,7 @@ } & .sheet{ - padding: 15px; + padding: @sheet-padding; // min-height: calc(@layout-min-height - 30px); // 280px; overflow: visible; // margin: 15px; @@ -126,3 +131,15 @@ .site-layout-background { background: #fff; } + +@media only screen and (max-width: 1280px) { + .page-layout { + --sheet-padding: 10px; + } +} + +@media only screen and (max-width: 1024px) { + .page-layout { + --sheet-padding: 5px; + } +} diff --git a/src/styles/loader.css b/src/styles/loader.css index 23ab350..33a233a 100755 --- a/src/styles/loader.css +++ b/src/styles/loader.css @@ -39,11 +39,18 @@ } .loader-content{ + display: flex; + flex-direction: column; grid-column-start: 1; grid-column-end: span 3; grid-row-start: 1; } +.loader-content.loader-content-fill { + height: 100%; + width: 100%; +} + .loader-overlay{ grid-column-start: 1; grid-column-end: span 3; diff --git a/src/styles/monitoring.less b/src/styles/monitoring.less deleted file mode 100644 index 9a3cb0e..0000000 --- a/src/styles/monitoring.less +++ /dev/null @@ -1,17 +0,0 @@ -.page-top { - display: flex; - flex-wrap: wrap; - margin: -5px; - - & > * { - margin: 5px; - } - - & > .icons { - display: flex; - - & > * { - margin-left: 15px; - } - } -} diff --git a/src/styles/telemetry_view.less b/src/styles/telemetry_view.less new file mode 100644 index 0000000..cdbb3c6 --- /dev/null +++ b/src/styles/telemetry_view.less @@ -0,0 +1,66 @@ +.telemetry-view-page { + --page-gap: 10px; + + @page-gap: var(--page-gap); + + .flex-container { + display: flex; + align-items: stretch; + justify-content: space-between; + gap: @page-gap; + } + + width: 100%; + height: 100%; + flex-direction: column; + .flex-container; + + & .page-top { + .flex-container; + flex-wrap: wrap; + justify-content: flex-start; + + & .icons { + display: flex; + + & > * { + margin-left: 15px; + } + } + } + + & .page-main { + flex-grow: 1; + .flex-container; + + & .page-left { + .flex-container; + justify-content: flex-start; + flex-direction: column; + + & .modes, & .current-values { + .flex-container; + flex-direction: column; + gap: 0; + } + } + + & .page-right { + flex-grow: 1; + .flex-container; + flex-direction: column; + } + } +} + +@media only screen and (max-width: 1280px) { + .telemetry-view-page { + --page-gap: 7.5px; + } +} + +@media only screen and (max-width: 1024px) { + .telemetry-view-page { + --page-gap: 5px; + } +} From cb17c6868cb18f261da45668b1e3150e37666a5f Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 15 Nov 2022 10:06:05 +0500 Subject: [PATCH 17/72] =?UTF-8?q?"=D0=94=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5"=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B0=D0=BD=D0=BE=20=D0=B2=20"=D0=9F=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=B0=D0=B4=20=D0=B4=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F"=20=D0=BD=D0=B0=20=D0=A0=D0=A2=D0=9A=20?= =?UTF-8?q?=D0=B8=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Well/WellOperations/DrillProcessFlow.jsx | 2 +- src/pages/Well/WellOperations/WellDrillParams.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Well/WellOperations/DrillProcessFlow.jsx b/src/pages/Well/WellOperations/DrillProcessFlow.jsx index 86dcb09..354b32b 100644 --- a/src/pages/Well/WellOperations/DrillProcessFlow.jsx +++ b/src/pages/Well/WellOperations/DrillProcessFlow.jsx @@ -10,7 +10,7 @@ import { arrayOrDefault } from '@utils' const columns = [ makeNumericStartEnd('Глубина, м', 'depth'), makeNumericMinMax('Нагрузка, т', 'axialLoad'), - makeNumericMinMax('Давление, атм', 'pressure'), + makeNumericMinMax('Перепад давления, атм', 'pressure'), makeNumericMinMax('Момент на ВСП, кН·м', 'rotorTorque'), makeNumericMinMax('Обороты на ВСП, об/мин', 'rotorSpeed'), makeNumericMinMax('Расход, л/с', 'flow') diff --git a/src/pages/Well/WellOperations/WellDrillParams.jsx b/src/pages/Well/WellOperations/WellDrillParams.jsx index 0ae6097..57b1ba8 100644 --- a/src/pages/Well/WellOperations/WellDrillParams.jsx +++ b/src/pages/Well/WellOperations/WellDrillParams.jsx @@ -26,7 +26,7 @@ export const getColumns = async (idWell) => { sorter: makeNumericSorter('idWellSectionType'), }), makeNumericAvgRange('Нагрузка, т', 'axialLoad', 1), - makeNumericAvgRange('Давление, атм', 'pressure', 1), + makeNumericAvgRange('Перепад давления, атм', 'pressure', 1), makeNumericAvgRange('Момент на ВСП, кН·м', 'rotorTorque', 1), makeNumericAvgRange('Обороты на ВСП, об/мин', 'rotorSpeed', 1), makeNumericAvgRange('Расход, л/с', 'flow', 1), From 9c9fc63bc2e9264301dfdf94f0780d9e468d7ef0 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 15 Nov 2022 10:32:47 +0500 Subject: [PATCH 18/72] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D1=91?= =?UTF-8?q?=D0=BD=20=D1=86=D0=B2=D0=B5=D1=82=20=D1=84=D0=BE=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D0=BD=D0=BE=D0=B9=20=D1=81=D0=BA=D0=B2=D0=B0=D0=B6=D0=B8?= =?UTF-8?q?=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/well_composite.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/well_composite.less b/src/styles/well_composite.less index ea64fc8..4d000cc 100644 --- a/src/styles/well_composite.less +++ b/src/styles/well_composite.less @@ -4,7 +4,7 @@ right: 0; top: 0; height: 100%; - background-color: red; + background-color: #FFF2F0; } & .avg-value { From b2c34d07a9b5509b31ac64592b30f32df396caae Mon Sep 17 00:00:00 2001 From: goodmice Date: Wed, 16 Nov 2022 11:29:53 +0500 Subject: [PATCH 19/72] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20replace=20=D0=B4=D0=BB=D1=8F=20handleDataS?= =?UTF-8?q?aub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Well/Telemetry/TelemetryView/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index 76b0bcf..f996d82 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -124,7 +124,7 @@ const TelemetryView = memo(() => { const dataSaub = await TelemetryDataSaubService.getData(well.id, null, chartInterval) const dataSpin = await TelemetryDataSpinService.getData(well.id, null, chartInterval) setFlowChartData(flowChart ?? []) - handleDataSaub(dataSaub) + handleDataSaub(dataSaub, true) handleDataSpin(dataSpin) }, null, From 11a632c246e93575d581bf91d16cfac29833f07a Mon Sep 17 00:00:00 2001 From: goodmice Date: Wed, 16 Nov 2022 11:58:03 +0500 Subject: [PATCH 20/72] =?UTF-8?q?*=20=D0=9C=D0=BE=D0=BD=D0=B8=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=20=D0=BD=D0=B0=20flex=20*=20=D0=91=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B8=D1=85=20?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BC=D0=B5=D1=89=D1=91=D0=BD=20=D0=BD=D0=B0=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=85=20=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=D0=BD=20*=20=D0=92=D1=8B=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=BE=D0=BC=20*=20=D0=A3?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BC=D0=BD=D0=B5=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=85=D0=B5=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Display.tsx | 101 -------- .../d3/monitoring/D3MonitoringCharts.tsx | 4 +- .../Telemetry/TelemetryView/CustomColumn.jsx | 58 ----- .../Telemetry/TelemetryView/ModeDisplay.jsx | 24 -- .../TelemetryView/TelemetrySummary.jsx | 107 ++++++++ .../Telemetry/TelemetryView/UserOfWells.jsx | 12 - .../Well/Telemetry/TelemetryView/index.jsx | 230 ++++++++++++------ src/styles/components/data_summary.less | 56 +++++ src/styles/display.less | 60 +++-- 9 files changed, 346 insertions(+), 306 deletions(-) delete mode 100644 src/components/Display.tsx delete mode 100644 src/pages/Well/Telemetry/TelemetryView/CustomColumn.jsx delete mode 100644 src/pages/Well/Telemetry/TelemetryView/ModeDisplay.jsx create mode 100644 src/pages/Well/Telemetry/TelemetryView/TelemetrySummary.jsx delete mode 100644 src/pages/Well/Telemetry/TelemetryView/UserOfWells.jsx create mode 100644 src/styles/components/data_summary.less diff --git a/src/components/Display.tsx b/src/components/Display.tsx deleted file mode 100644 index a423bab..0000000 --- a/src/components/Display.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import moment from 'moment' -import { useState, useEffect, memo, ReactNode } from 'react' -import {CaretUpOutlined, CaretDownOutlined, CaretRightOutlined} from '@ant-design/icons' - -import '@styles/display.less' - -export const formatNumber = (value?: unknown, format?: number) => - Number.isInteger(format) && Number.isFinite(value) - ? Number(value).toFixed(format) - : Number(value).toPrecision(4) - -const iconStyle = { color:'#0008' } -const displayValueStyle = { display: 'flex', flexGrow: 1 } - -export type ValueDisplayProps = { - prefix?: ReactNode - suffix?: ReactNode - format?: number | string | ((arg: string) => ReactNode) - isArrowVisible?: boolean - enumeration?: Record - value: string -} - -export type DisplayProps = ValueDisplayProps & { - className?: string - label?: ReactNode -} - -export const ValueDisplay = memo(({ prefix, value, suffix, isArrowVisible, format, enumeration }) => { - const [val, setVal] = useState('---') - const [arrowState, setArrowState] = useState({ - preVal: NaN, - preTimestamp: Date.now(), - direction: 0, - }) - - useEffect(() => { - setVal((preVal) => { - if ((value ?? '-') === '-' || value === '--') return '---' - if (typeof format === 'function') return format(enumeration?.[value] ?? value) - if (enumeration?.[value]) return enumeration[value] - - if (Number.isFinite(+value)) { - if (isArrowVisible && (arrowState.preTimestamp + 1000 < Date.now())) { - let direction = 0 - if (+value > arrowState.preVal) - direction = 1 - if (+value < arrowState.preVal) - direction = -1 - - setArrowState({ - preVal: +value, - preTimestamp: Date.now(), - direction: direction, - }) - } - - return formatNumber(value, Number(format)) - } - - if (value.length > 4) { - const valueDate = moment(value) - if (valueDate.isValid()) - return valueDate.format(String(format)) - } - - return value - }) - },[value, isArrowVisible, arrowState, format, enumeration]) - - let arrow = null - if(isArrowVisible) - switch (arrowState.direction){ - case 0: - arrow = - break - case 1: - arrow = - break - case -1: - arrow = - break - default: - break - } - - return( - - {prefix} {val} {suffix}{arrow} - - ) -}) - -export const Display = memo(({ className, label, ...other })=> ( -
-
{label}
-
- -
-
-)) diff --git a/src/components/d3/monitoring/D3MonitoringCharts.tsx b/src/components/d3/monitoring/D3MonitoringCharts.tsx index bfeb30b..0f6b320 100644 --- a/src/components/d3/monitoring/D3MonitoringCharts.tsx +++ b/src/components/d3/monitoring/D3MonitoringCharts.tsx @@ -73,8 +73,8 @@ export type ChartGroup = { } const defaultOffsets: ChartOffset = { - top: 10, - bottom: 10, + top: 0, + bottom: 0, left: 100, right: 20, } diff --git a/src/pages/Well/Telemetry/TelemetryView/CustomColumn.jsx b/src/pages/Well/Telemetry/TelemetryView/CustomColumn.jsx deleted file mode 100644 index 9bc1cc5..0000000 --- a/src/pages/Well/Telemetry/TelemetryView/CustomColumn.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import moment from 'moment' -import { memo } from 'react' -import { Tooltip, Typography } from 'antd' - -import { Display } from '@components/Display' - -import RigMnemo from './RigMnemo' - -const getTimeFormat = (value) => { - const date = moment(value) - - return ( - - {date.isSame(new Date(), 'day') || ( - {date.format('DD.MM.YYYY')} - )} - {date.format('HH:mm:ss')} - - ) -} - -const params = [ - { label: 'Рот., об/мин', accessorName: 'rotorSpeed', isArrowVisible: true }, - { label: 'Долото, м', accessorName: 'bitDepth', isArrowVisible: true, format: 2 }, - { label: 'Забой, м', accessorName: 'wellDepth', isArrowVisible: true, format: 2 }, - { label: 'Расход, м³/ч', accessorName: 'flow', isArrowVisible: true }, - { label: 'Расход х.х., м³/ч', accessorName: 'flowIdle', isArrowVisible: true }, - { label: 'Время', accessorName: 'date', format: getTimeFormat }, - { label: 'MSE, %', accessorName: 'mse', format: 2 }, -] - -export const CustomColumn = memo(({ data }) => { - const dataLast = data[data.length - 1] - params.forEach(param => param.value = dataLast?.[param.accessorName] ?? '-') - - return ( - <> - {params.map(param => ( - - ))} - - - ) -}) - -export default CustomColumn diff --git a/src/pages/Well/Telemetry/TelemetryView/ModeDisplay.jsx b/src/pages/Well/Telemetry/TelemetryView/ModeDisplay.jsx deleted file mode 100644 index 50ee80e..0000000 --- a/src/pages/Well/Telemetry/TelemetryView/ModeDisplay.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { memo } from 'react' - -import { Display } from '@components/Display' - -const modeNames = { - 0: 'Ручной', - 1: 'Бурение в роторе', - 2: 'Проработка', - 3: 'Бурение в слайде', - 4: 'Спуск СПО', - 5: 'Подъем СПО', - 6: 'Подъем с проработкой', - - 10: 'БЛОКИРОВКА', -} - -export const ModeDisplay = memo(({ data }) => ( - -)) diff --git a/src/pages/Well/Telemetry/TelemetryView/TelemetrySummary.jsx b/src/pages/Well/Telemetry/TelemetryView/TelemetrySummary.jsx new file mode 100644 index 0000000..f85fd09 --- /dev/null +++ b/src/pages/Well/Telemetry/TelemetryView/TelemetrySummary.jsx @@ -0,0 +1,107 @@ +import { isValidElement, memo, useEffect, useMemo, useState } from 'react' +import { CaretUpOutlined, CaretDownOutlined, CaretRightOutlined } from '@ant-design/icons' +import { Tooltip, Typography } from 'antd' +import moment from 'moment' + +import { formatDate, isRawDate } from '@utils' + +import '@styles/components/data_summary.less' + +export const parseValue = (value, formatter) => { + if (!value || String(value).trim().length <= 0) return '---' + if (typeof formatter === 'function') return formatter(value) + if (isRawDate(value)) return formatDate(value) + const v = +value + if (Number.isFinite(v)) + return Number.isInteger(formatter) ? v.toFixed(formatter) : v.toPrecision(4) + return value +} + +export const DashboardDisplay = memo(({ label, title, unit, iconRenderer, value, format }) => { + const [icon, setIcon] = useState(null) + + const val = useMemo(() => parseValue(value, format), [value, format]) + + useEffect(() => setIcon((prev) => iconRenderer?.(value, prev)), [value]) + + return ( +
+
+ {title ? ( + {label} + ) : ( + {label} + )} + {unit} +
+
+ {val} + {isValidElement(icon) ? icon : icon?.value} +
+
+ ) +}) + +const getTimeFormat = (value) => { + const date = moment(value) + + return ( + + {date.isSame(new Date(), 'day') || ( + {date.format('DD.MM.YYYY')} + )} + {date.format('HH:mm:ss')} + + ) +} + +const iconRenderer = (value, prev) => { + if (!Number.isFinite(+value)) return null + if (prev?.prevDate + 1000 >= Date.now()) return prev + const val = +value + let Component = CaretRightOutlined + if ((prev?.prev ?? null) && val !== prev.prev) + Component = val > prev.prev ? CaretUpOutlined : CaretDownOutlined + + return { + prev: val, + prevDate: Date.now(), + value: , + } +} + +const modeNames = { + 0: 'Ручной', + 1: 'Бурение в роторе', + 2: 'Проработка', + 3: 'Бурение в слайде', + 4: 'Спуск СПО', + 5: 'Подъем СПО', + 6: 'Подъем с проработкой', + + 10: 'БЛОКИРОВКА', +} + +const params = [ + { label: 'Режим', accessorName: 'mode', format: (value) => modeNames[value] || '---' }, + { label: 'Пользователь', accessorName: 'user', title: 'Пользователь панели оператора' }, + { label: 'Рот.', unit: 'об/мин', accessorName: 'rotorSpeed', iconRenderer }, + { label: 'Долото', unit: 'м', accessorName: 'bitDepth', iconRenderer, format: 2 }, + { label: 'Забой', unit: 'м', accessorName: 'wellDepth', iconRenderer, format: 2 }, + { label: 'Расход', unit: 'м³/ч', accessorName: 'flow', iconRenderer }, + { label: 'Расход х.х.', unit: 'м³/ч', accessorName: 'flowIdle', iconRenderer }, + { label: 'Время', accessorName: 'date', format: getTimeFormat }, + { label: 'MSE', unit: '%', accessorName: 'mse', format: 2 }, +] + +export const TelemetrySummary = memo(({ data }) => { + return ( +
+ {params.map((param, i) => ( + + ))} +
+ ) +}) + +export default TelemetrySummary diff --git a/src/pages/Well/Telemetry/TelemetryView/UserOfWells.jsx b/src/pages/Well/Telemetry/TelemetryView/UserOfWells.jsx deleted file mode 100644 index f25217c..0000000 --- a/src/pages/Well/Telemetry/TelemetryView/UserOfWells.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Tooltip } from 'antd' -import { Display } from '@components/Display' - -export const UserOfWell = ({ data }) => ( - Пользователь} - value={data[data.length - 1]?.user} - /> -) - -export default UserOfWell diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index f996d82..ff43210 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -1,15 +1,15 @@ import { useState, useEffect, useCallback, memo, useMemo } from 'react' import { BehaviorSubject, buffer, throttleTime } from 'rxjs' +import { useSearchParams } from 'react-router-dom' import { Button, Select } from 'antd' import { useWell } from '@asb/context' -import { makeDateSorter } from '@components/Table' +import { DatePickerWrapper, makeDateSorter } from '@components/Table' import { D3MonitoringCharts } from '@components/d3/monitoring' import LoaderPortal from '@components/LoaderPortal' -import { Grid, GridItem } from '@components/Grid' import { invokeWebApiWrapperAsync } from '@components/factory' import { PeriodPicker, defaultPeriod } from '@components/selectors/PeriodPicker' -import { formatDate, hasPermission, withPermissions } from '@utils' +import { formatDate, hasPermission, isRawDate, range, withPermissions } from '@utils' import { Subscribe } from '@services/signalr' import { DrillFlowChartService, @@ -20,10 +20,8 @@ import { import { makeChartGroups, yAxis } from './dataset' import ActiveMessagesOnline from './ActiveMessagesOnline' +import TelemetrySummary from './TelemetrySummary' import WirelineRunOut from './WirelineRunOut' -import { CustomColumn } from './CustomColumn' -import { ModeDisplay } from './ModeDisplay' -import { UserOfWell } from './UserOfWells' import { Setpoints } from './Setpoints' import { cursorRender } from './cursorRender' @@ -37,6 +35,14 @@ import '@styles/message.less' const { Option } = Select +const chartProps = { + yAxis, + chartName: 'monitoring', + yTicks: { visible: true, format: (d) => formatDate(d, 'YYYY-MM-DD') }, + plugins: { menu: { enabled: false }, cursor: { render: cursorRender } }, + style: { flexGrow: 1, height: 'auto', width: 'auto' }, +} + const getLast = (data) => Array.isArray(data) ? data.at(-1) : data const isMseEnabled = (dataSaub) => (getLast(dataSaub)?.mseState && 2) > 0 @@ -54,6 +60,7 @@ export const normalizeData = (data) => data?.map(item => ({ })) ?? [] const dateSorter = makeDateSorter('date') +const defaultDate = () => Date.now() - defaultPeriod * 1000 const makeSubjectSubsription = (subject$, handler) => { const subscribtion = subject$.pipe( @@ -64,20 +71,49 @@ const makeSubjectSubsription = (subject$, handler) => { } const TelemetryView = memo(() => { + const [well, updateWell] = useWell() + const [searchParams, setSearchParams] = useSearchParams() + const [currentWellId, setCurrentWellId] = useState(null) const [dataSaub, setDataSaub] = useState([]) const [dataSpin, setDataSpin] = useState([]) - const [chartInterval, setChartInterval] = useState(defaultPeriod) const [showLoader, setShowLoader] = useState(false) const [flowChartData, setFlowChartData] = useState([]) const [rop, setRop] = useState(null) const [domain, setDomain] = useState({}) const [chartMethods, setChartMethods] = useState() - const [well, updateWell] = useWell() + const [chartInterval, setChartInterval] = useState(defaultPeriod) + const [startDate, setStartDate] = useState(defaultDate) + const [dateLimit, setDateLimit] = useState({ from: 0, to: new Date() }) - const saubSubject$ = useMemo(() => new BehaviorSubject(), []) - const spinSubject$ = useMemo(() => new BehaviorSubject(), []) + const [archiveMode, setArchiveMode] = useState(false) + + const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well]) + + const isDateDisabled = useCallback((date) => { + if (!date) return false + const dt = new Date(date).setHours(0, 0, 0, 0) + return dt < dateLimit.from || dt > +dateLimit.to - chartInterval + }, [dateLimit, chartInterval]) + + const isDateTimeDisabled = useCallback((date) => ({ + disabledHours: () => range(24).filter(h => { + if (!date) return false + const dt = +new Date(date).setHours(h) + return dt < dateLimit.from || dt > +dateLimit.to - chartInterval + }), + disabledMinutes: () => range(60).filter(m => { + if (!date) return false + const dt = +new Date(date).setMinutes(m) + return dt < dateLimit.from || dt > +dateLimit.to - chartInterval + }), + disabledSeconds: () => range(60).filter(s => { + if (!date) return false + const dt = +new Date(date).setSeconds(s) + return dt < dateLimit.from || dt > +dateLimit.to - chartInterval + }) + }), [dateLimit, chartInterval]) const handleDataSaub = useCallback((data, replace = false) => { setDataSaub((prev) => { @@ -92,7 +128,21 @@ const TelemetryView = memo(() => { const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), []) - const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well]) + const onWheel = useCallback((value) => { + if (!archiveMode && value.deltaY < 0) { + setArchiveMode(true) + // load data + } else { + // move + } + }, [archiveMode]) + + const spinLast = useMemo(() => dataSpin.at(-1), [dataSpin]) + const saubLast = useMemo(() => dataSaub.at(-1), [dataSaub]) + const summaryData = useMemo(() => ({ ...saubLast, ...rop }), [saubLast, rop]) + + const saubSubject$ = useMemo(() => new BehaviorSubject(), []) + const spinSubject$ = useMemo(() => new BehaviorSubject(), []) const filteredData = useMemo(() => { let i, j @@ -112,6 +162,32 @@ const TelemetryView = memo(() => { const chartGroups = useMemo(() => makeChartGroups(flowChartData), [flowChartData]) + useEffect(() => { + setArchiveMode(isRawDate(searchParams.get('start'))) + const interval = parseInt(searchParams.get('range') || defaultPeriod) + const date = new Date(searchParams.get('start') || (Date.now() - interval)) + setChartInterval(interval) + setStartDate(date) + }, [searchParams]) + + useEffect(() => { + if (archiveMode) return + const subscribtion = saubSubject$.pipe( + buffer(saubSubject$.pipe(throttleTime(700))) + ).subscribe((data) => handleDataSaub(data.flat().filter(Boolean))) + + return () => subscribtion.unsubscribe() + }, [saubSubject$, archiveMode]) + + useEffect(() => { + if (archiveMode) return + const subscribtion = spinSubject$.pipe( + buffer(spinSubject$.pipe(throttleTime(700))) + ).subscribe((data) => handleDataSpin(data.flat().filter(Boolean))) + + return () => subscribtion.unsubscribe() + }, [spinSubject$, archiveMode]) + useEffect(() => makeSubjectSubsription(saubSubject$, handleDataSaub), [saubSubject$, handleDataSaub]) useEffect(() => makeSubjectSubsription(spinSubject$, handleDataSpin), [spinSubject$, handleDataSpin]) @@ -148,6 +224,12 @@ const TelemetryView = memo(() => { async () => { const rop = await OperationStatService.getClusterRopStatByIdWell(well.id) setRop(rop) + let dates = await TelemetryDataSaubService.getDataDatesRange(well.id) + dates = { + from: new Date(dates?.from ?? 0), + to: new Date(dates?.to ?? 0) + } + setDateLimit(dates) }, setShowLoader, `Не удалось загрузить данные`, @@ -156,74 +238,66 @@ const TelemetryView = memo(() => { }, [well]) useEffect(() => { - if (dataSaub.length <= 0) return - const last = new Date(dataSaub.at(-1).date) - setDomain({ - min: new Date(+last - chartInterval * 1000), - max: last - }) - }, [dataSaub, chartInterval]) + if (!saubLast || archiveMode) return + const last = new Date(saubLast.date) + const startDate = new Date(+last - chartInterval * 1000) + setStartDate(startDate) + setDomain({ min: startDate, max: last }) + }, [archiveMode, saubLast, chartInterval]) - return ( - - - -
- -
- Интервал:  - -
- -
- Статус:  - -
- -   - -
- {'TorqueMaster'} - {'SpinMaster'} -

MSE

-
- -
-
- - - - - formatDate(d, 'YYYY-MM-DD') - }} - plugins={{ - menu: { enabled: false }, - cursor: { - render: cursorRender, - }, - }} - height={'70vh'} - /> - - - - -
-
- ) + return ( + +
+ +
+
+ Статус:  + +
+ + +
+ {'TorqueMaster'} + {'SpinMaster'} +

MSE

+
+
+
+
+ Начальная дата:  + setStartDate(new Date(startDate))} + /> +
+
+ Интервал:  + +
+ + +
+ + +
+
+ ) }) export default withPermissions(TelemetryView, [ diff --git a/src/styles/components/data_summary.less b/src/styles/components/data_summary.less new file mode 100644 index 0000000..cbb1eac --- /dev/null +++ b/src/styles/components/data_summary.less @@ -0,0 +1,56 @@ +.data-summary { + display: flex; + gap: .5em; + flex-wrap: wrap; + justify-content: flex-start; +} + +.dashboard-display { + display: flex; + flex-direction: column; + align-items: stretch; + border: .067em solid #D9D9D9; + gap: .2em; + border-radius: .14em; + padding: .3em; + min-width: 7.5em; + + & .display-label { + gap: 1.5em; + display: flex; + justify-content: space-between; + font-size: .75em; + line-height: 1em; + color: rgba(0, 0, 0, .3); + } + + & .display-value { + display: flex; + justify-content: flex-end; + gap: .1em; + font-size: 1.3em; + line-height: 1em; + font-weight: bold; + color: rgba(0, 0, 0, .85); + } +} + +@media only screen and (max-width: 1280px) { + .data-summary { + gap: .35em; + } + + .dashboard-display { + font-size: 14px; + } +} + +@media only screen and (max-width: 1150px) { + .data-summary { + gap: .2em; + } + + .dashboard-display { + font-size: 12.5px; + } +} diff --git a/src/styles/display.less b/src/styles/display.less index 85d329f..ed55118 100644 --- a/src/styles/display.less +++ b/src/styles/display.less @@ -1,7 +1,6 @@ .display_flex_container{ display: flex; - flex-wrap: wrap; flex: auto; } .display_header { @@ -38,37 +37,36 @@ } } -.display_label{ - font-size: 16px; - color: rgb(70, 70, 70); - text-align: left; - justify-content: center; - margin: 1px 0 1px 1rem; - flex: auto; - align-items: baseline; - text-overflow: ellipsis; - overflow-x: hidden; - overflow-y: hidden; - display: -webkit-box; - -webkit-box-orient: vertical; - height: 30px; +.display { + display: flex; + justify-content: space-between; + column-gap: 20px; + padding: 1px 1rem; + align-items: stretch; + + & .display_label{ + font-size: 16px; + color: rgb(70, 70, 70); + text-overflow: ellipsis; + overflow: hidden; + flex-grow: 1; + } + + & .display_value{ + display: flex; + align-items: center; + font-size: 18px; + font-weight: bold; + color: rgb(50, 50, 50); + } } -.display_value{ - font-size: 18px; - font-weight: bold; - color: rgb(50, 50, 50); - text-align: right; - justify-content: flex-end; - align-items:baseline; - margin: 1px 1rem; - flex: auto; -} +@media only screen and (max-width: 1280px) { + .display { + padding: 1px 5px; + } -.display_small_value{ - color: rgb(50, 50, 50); - text-align: right; - justify-content: center; - margin: 1px 1rem 1px 1rem; - flex: auto; + .display_flex_container { + flex-wrap: wrap; + } } From 3216a90af3d159a6627f038cc5f2595c97a3bfab Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 21 Nov 2022 08:47:59 +0500 Subject: [PATCH 21/72] =?UTF-8?q?=D0=9A=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BE=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20URL=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D1=81=D0=BA=D0=B0=D0=B7=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CopyUrl.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/CopyUrl.tsx b/src/components/CopyUrl.tsx index 22e7747..2a157f4 100644 --- a/src/components/CopyUrl.tsx +++ b/src/components/CopyUrl.tsx @@ -1,5 +1,5 @@ import { cloneElement, memo, useCallback, useMemo, useState } from 'react' -import { Button, ButtonProps } from 'antd' +import { Button, ButtonProps, Tooltip } from 'antd' import { CopyOutlined } from '@ant-design/icons' import { invokeWebApiWrapperAsync, notify } from './factory' @@ -43,11 +43,9 @@ export type CopyUrlButtonProps = Omit & ButtonProps export const CopyUrlButton = memo(({ sendLoading, hideUnsupported, onCopy, ...other }) => { return ( -