Merge branch 'add-cluster-layer'

This commit is contained in:
Фролов 2021-07-22 10:35:55 +05:00
commit 745a41a051
23 changed files with 368 additions and 146 deletions

55
package-lock.json generated
View File

@ -10473,8 +10473,7 @@
"dependencies": {
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"resolved": "",
"dev": true
},
"normalize-package-data": {
@ -11657,6 +11656,11 @@
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
},
"pigeon-maps": {
"version": "0.19.7",
"resolved": "https://registry.npmjs.org/pigeon-maps/-/pigeon-maps-0.19.7.tgz",
"integrity": "sha512-8yS5o7lr3mr5674Cfkxrk3EqPiV6WjGBrpVKNn6/40bTdH5qq2NNXK2Iu0fQcDRRZ6mTtXHX0tJAO1tp8ZKBNg=="
},
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@ -11797,9 +11801,9 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"version": "7.0.36",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
@ -14137,9 +14141,9 @@
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
},
"resolve-url-loader": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz",
"integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==",
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.4.tgz",
"integrity": "sha512-D3sQ04o0eeQEySLrcz4DsX3saHfsr8/N6tfhblxgZKXxMT2Louargg12oGNfoTRLV09GXhVUe5/qgA5vdgNigg==",
"requires": {
"adjust-sourcemap-loader": "3.0.0",
"camelcase": "5.3.1",
@ -14147,7 +14151,7 @@
"convert-source-map": "1.7.0",
"es6-iterator": "2.0.3",
"loader-utils": "1.2.3",
"postcss": "7.0.21",
"postcss": "7.0.36",
"rework": "1.0.1",
"rework-visit": "1.0.0",
"source-map": "0.6.1"
@ -14181,28 +14185,10 @@
"json5": "^1.0.1"
}
},
"postcss": {
"version": "7.0.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz",
"integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==",
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
@ -15905,9 +15891,9 @@
}
},
"trim-newlines": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz",
"integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
"integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
"dev": true
},
"tryer": {
@ -16862,8 +16848,7 @@
},
"ssri": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
"resolved": "",
"requires": {
"figgy-pudding": "^3.5.1"
}
@ -17804,9 +17789,9 @@
}
},
"ws": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw=="
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg=="
},
"xml-name-validator": {
"version": "3.0.0",

View File

@ -33,7 +33,7 @@
"react_test": "react-scripts test",
"eject": "react-scripts eject"
},
"proxy": "http://192.168.1.70:5000",
"proxy": "http://127.0.0.1:5000",
"eslintConfig": {
"extends": [
"react-app",

BIN
src/images/las.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
src/images/pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

12
src/images/pointer.svg Normal file
View File

@ -0,0 +1,12 @@
<svg width="48" height="48" version="1.1" viewBox="0 0 7.9 7.9" xmlns="http://www.w3.org/2000/svg" xmlns:osb="http://www.openswatchbook.org/uri/2009/osb">
<g stroke="#000">
<path d="m3.1 1.6 1.9 1.5-2.3 1.1 2.9 2-0.78-4.6h-1.7l-0.78 4.6 2.9-2-2.3-1.1 1.9-1.5" fill="none" stroke-linecap="round" stroke-linejoin="bevel" stroke-width=".17"/>
<rect x="2.8" y=".98" width="2.3" height=".45" stroke-width=".084"/>
<rect x="3.4" y=".31" width="1.2" height=".45" stroke-width=".084"/>
<g fill="none" stroke-width=".084">
<rect x="3.9" y="1.6" width=".11" height="6" stroke-linejoin="round"/>
<path d="m1.2 6.2 2.1 8e-7v1.5"/>
<path d="m4.5 7.7v-1.5l1.9-8e-7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 691 B

BIN
src/images/xlsx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

143
src/pages/Cluster.jsx Normal file
View File

@ -0,0 +1,143 @@
import {useParams} from "react-router-dom";
import {Link} from "react-router-dom";
import LoaderPortal from '../components/LoaderPortal'
import { useState, useEffect } from "react";
import {ClusterService} from '../services/api'
import notify from '../components/notify'
import {Table, Tag, Button} from 'antd';
const columns = [
{
title: 'скв №',
key: 'caption',
dataIndex: 'caption',
render: (_, item) => <Link to={`/well/${item.id}`}>{item.caption}</Link>
},
{
title: 'Тип скв.',
key: 'wellType',
dataIndex: 'wellType',
},
{
title: 'Фактические сроки бурения',
children: [
{
title: 'начало',
key: 'factStart',
dataIndex: 'factStart',
},
{
title: 'окончание',
key: 'factEnd',
dataIndex: 'factEnd',
},
]
},
{
title: 'Продолжительность бурения',
children: [
{
title: 'план',
key: 'periodPlan',
dataIndex: 'periodPlan',
},
{
title: 'факт',
key: 'periodFact',
dataIndex: 'periodFact',
},
]
},
{
title: 'МСП за скв',
children: [
{
title: 'план',
key: 'rateOfPenetrationPlan',
dataIndex: 'rateOfPenetrationPlan',
},
{
title: 'факт',
key: 'rateOfPenetrationFact',
dataIndex: 'rateOfPenetrationFact',
},
],
},
{
title: 'Рейсовая скорость за скв',
children: [
{
title: 'план',
key: 'routeSpeedPlan',
dataIndex: 'routeSpeedPlan',
},
{
title: 'факт',
key: 'routeSpeedFact',
dataIndex: 'routeSpeedFact',
},
],
},
{
title: 'Секции',
key: 'sections',
dataIndex: 'sections',
render: (item) => (<span>таблица по секциям</span>)
},
{
title: 'График глубина-день',
render: _ => (<Button>Открыть</Button>)
},
{
title: 'Таблица по операциям',
render: _ => (<Button>Открыть</Button>)
},
{
title: 'Подрядчики',
key: 'companies',
dataIndex: 'companies',
render: (item) => item.map(company => <Tag color="blue">{company.caption}</Tag>)
},
];
export default function Cluster() {
let { id } = useParams()
const [clusterTitle, setClusterTitle] = useState("")
const [wellsStat, setWellsStat] = useState(null)
const [showLoader, setShowLoader] = useState(false)
useEffect(()=>{
const updateWellsStat = async() => {
setShowLoader(true)
try{
const msInDay = 1000*60*60*24
const data = await ClusterService.getStat(id)
const wellsStat = data.wellsStat.map(w=>({...w,
periodPlan: (new Date(w.planEnd) - new Date(w.planStart))/msInDay,
periodFact: (new Date(w.factEnd) - new Date(w.factStart))/msInDay,
}))
setWellsStat(wellsStat)
setClusterTitle(data.caption)
}
catch(ex) {
notify(`Не удалось загрузить статистику по скважинам куста "${id}"`, 'error')
console.log(ex)
}
setShowLoader(false)
}
updateWellsStat()
},[id])
return(
<LoaderPortal show={showLoader}>
<h3>{clusterTitle}</h3>
<Table
columns={columns}
dataSource={wellsStat}
size={'small'}
bordered
pagination={false}
rowKey={(record) => record.id}
/>
</LoaderPortal>)
}

72
src/pages/Deposit.jsx Normal file
View File

@ -0,0 +1,72 @@
import { Map, Overlay } from "pigeon-maps"
import pointer from '../images/pointer.svg'
import {Link} from "react-router-dom";
import LoaderPortal from '../components/LoaderPortal'
import { useState, useEffect } from "react";
import {ClusterService} from '../services/api'
import notify from '../components/notify'
const calcViewParams = (clusters) => {
if (clusters.length === 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 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)
return {center, zoom: maxDeg*25}
}
export default function Deposit() {
const [clustersData, setClustersData] = useState([])
const [showLoader, setShowLoader] = useState(false)
const updateClusters = async()=>{
setShowLoader(true)
try{
const data = await ClusterService.getClusters()
setClustersData(data)
}
catch(ex) {
notify(`Не удалось загрузить список кустов`, 'error')
console.log(ex)
}
setShowLoader(false)
}
useEffect(()=>{updateClusters()}, [])
const viewParams = calcViewParams(clustersData)
return (
<LoaderPortal show={showLoader}>
<Map
height='100vh'
center={viewParams.center}
zoom={viewParams.zoom}
>
{clustersData.map(cluster =>
<Overlay
width={32}
anchor={[cluster.latitude, cluster.longitude]}
key={`${cluster.latitude} ${cluster.longitude}`}
>
<Link to={`/cluster/${cluster.id}`}>
<img width={40} src={pointer} alt="+"/>
<span>{cluster.caption}</span>
</Link>
</Overlay >)}
</Map>
</LoaderPortal>
);
}

View File

@ -1,25 +1,31 @@
import Wells from './Wells'
import Well from "../components/Well";
import Deposit from './Deposit'
import Cluster from './Cluster'
import Well from "./Well";
import LayoutPortal from './LayoutPortal'
import {Redirect, Route, Switch} from "react-router-dom";
export default function Main() {
return (
<Switch>
<Route path="/well/:id/">
<LayoutPortal>
<Well/>
</LayoutPortal>
</Route>
<Route path="/well">
<LayoutPortal>
<Wells/>
</LayoutPortal>
</Route>
<Route path="/">
<Redirect to={{pathname: `/well`}}/>
</Route>
</Switch>
return (
<Switch>
<Route path="/well/:id/">
<LayoutPortal>
<Well/>
</LayoutPortal>
</Route>
<Route path="/deposit">
<LayoutPortal>
<Deposit/>
</LayoutPortal>
</Route>
<Route path="/cluster/:id/">
<LayoutPortal>
<Cluster/>
</LayoutPortal>
</Route>
<Route path="/">
<Redirect to={{pathname: `/deposit`}}/>
</Route>
</Switch>
)
}

View File

@ -1,65 +0,0 @@
import { useState, useEffect } from 'react'
import { WellService } from '../services/api'
import LoaderPortal from '../components/LoaderPortal'
import { Table } from 'antd' // TreeSelect
import { useHistory } from 'react-router-dom'
import notify from '../components/notify'
const columns = [
{
title: 'Месторождение',
dataIndex: 'deposit',
key: 'deposit',
},
{
title: 'Куст',
dataIndex: 'cluster',
key: 'cluster',
},
{
title: 'Скважина',
dataIndex: 'caption',
key: 'caption',
},
{
title: 'Данные',
dataIndex: 'lastData',
key: 'lastData',
},
];
export default function Wells(props){
const [wells, setWells] = useState([])
const [loader, setLoader] = useState(false)
const history = useHistory()
const updateWellsList = async () => {
setLoader(true)
try{
let newWells = (await WellService.getWells()).map(w =>{return {key:w.id, ...w}})
console.log(newWells)
setWells( newWells )
}
catch(e){
notify('Не удалось загрузить список скважин', 'error')
console.error(`${e}`);
}
setLoader(false)
}
useEffect(()=>updateWellsList(), [])
return(<>
<h2>Скважины</h2>
<LoaderPortal show={loader}>
<Table
dataSource={wells}
columns={columns}
onRow={(record) => {
return {
onClick: event => {history.push(`/well/${record.id}/`)},
};
}}/>
</LoaderPortal>
</>)
}

View File

@ -5,8 +5,9 @@ export { ApiError } from './core/ApiError';
export { OpenAPI } from './core/OpenAPI';
export type { AuthDto } from './models/AuthDto';
export type { ClusterAnalysisDto } from './models/ClusterAnalysisDto';
export type { ClusterDto } from './models/ClusterDto';
export type { ClusterStatDto } from './models/ClusterStatDto';
export type { CompanyDto } from './models/CompanyDto';
export type { DataSaubBaseDto } from './models/DataSaubBaseDto';
export type { DatesRangeDto } from './models/DatesRangeDto';
export type { DepositDto } from './models/DepositDto';
@ -23,6 +24,8 @@ export type { UserTokenDto } from './models/UserTokenDto';
export type { WellDepthToDayDto } from './models/WellDepthToDayDto';
export type { WellDepthToIntervalDto } from './models/WellDepthToIntervalDto';
export type { WellDto } from './models/WellDto';
export type { WellSectionDto } from './models/WellSectionDto';
export type { WellStatDto } from './models/WellStatDto';
export { AnalyticsService } from './services/AnalyticsService';
export { AuthService } from './services/AuthService';

View File

@ -1,7 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ClusterAnalysisDto = {
id?: number;
}

View File

@ -4,8 +4,8 @@
export type ClusterDto = {
id?: number;
name?: string | null;
caption?: string | null;
description?: string | null;
latitude?: number;
longitude?: number;
latitude?: number | null;
longitude?: number | null;
}

View File

@ -0,0 +1,14 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { WellStatDto } from './WellStatDto';
export type ClusterStatDto = {
id?: number;
caption?: string | null;
description?: string | null;
latitude?: number | null;
longitude?: number | null;
wellsStat?: Array<WellStatDto> | null;
}

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CompanyDto = {
id?: number;
caption?: string | null;
companyType?: string | null;
}

View File

@ -6,6 +6,6 @@ export type DepositDto = {
id?: number;
name?: string | null;
description?: string | null;
latitude?: number;
longitude?: number;
latitude?: number | null;
longitude?: number | null;
}

View File

@ -9,7 +9,7 @@ export type UserTokenDto = {
surname?: string | null;
patronymic?: string | null;
id?: number;
customerName?: string | null;
companyName?: string | null;
roleName?: string | null;
token?: string | null;
}

View File

@ -8,6 +8,7 @@ export type WellDto = {
deposit?: string | null;
id?: number;
lastData?: any;
latitude?: number;
longitude?: number;
latitude?: number | null;
longitude?: number | null;
wellType?: string | null;
}

View File

@ -0,0 +1,21 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WellSectionDto = {
sectionType?: string | null;
wellDepthPlan?: number;
wellDepthFact?: number;
buildDaysPlan?: number;
buildDaysFact?: number;
rateOfPenetrationPlan?: number;
rateOfPenetrationFact?: number;
routeSpeedPlan?: number;
routeSpeedFact?: number;
bhaUpSpeedPlan?: number;
bhaUpSpeedFact?: number;
bhaDownSpeedPlan?: number;
bhaDownSpeedFact?: number;
casingDownSpeedPlan?: number;
casingDownSpeedFact?: number;
}

View File

@ -0,0 +1,28 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CompanyDto } from './CompanyDto';
import type { WellSectionDto } from './WellSectionDto';
export type WellStatDto = {
caption?: string | null;
cluster?: string | null;
deposit?: string | null;
id?: number;
lastData?: any;
latitude?: number | null;
longitude?: number | null;
wellType?: string | null;
planStart?: string | null;
planEnd?: string | null;
factStart?: string | null;
factEnd?: string | null;
unProductiveDays?: number | null;
rateOfPenetrationPlan?: number | null;
rateOfPenetrationFact?: number | null;
routeSpeedPlan?: number | null;
routeSpeedFact?: number | null;
sections?: Array<WellSectionDto> | null;
companies?: Array<CompanyDto> | null;
}

View File

@ -1,8 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ClusterAnalysisDto } from '../models/ClusterAnalysisDto';
import type { ClusterDto } from '../models/ClusterDto';
import type { ClusterStatDto } from '../models/ClusterStatDto';
import type { WellDto } from '../models/WellDto';
import { request as __request } from '../core/request';
@ -23,32 +23,32 @@ export class ClusterService {
/**
* Получение доступных пользователю скважин
* @param clusterId
* @param idCluster
* @returns WellDto Success
* @throws ApiError
*/
public static async getWells(
clusterId: number,
idCluster: number,
): Promise<Array<WellDto>> {
const result = await __request({
method: 'GET',
path: `/api/cluster/${clusterId}`,
path: `/api/cluster/${idCluster}`,
});
return result.body;
}
/**
* Получение обопщенной аналитики по кусту (лучшая/худшая скважина)
* @param clusterId
* @returns ClusterAnalysisDto Success
* Получение обопщенной статистики по кусту (лучшая/худшая скважина)
* @param idCluster
* @returns ClusterStatDto Success
* @throws ApiError
*/
public static async getAnalysis(
clusterId: number,
): Promise<Array<ClusterAnalysisDto>> {
public static async getStat(
idCluster: number,
): Promise<ClusterStatDto> {
const result = await __request({
method: 'GET',
path: `/api/cluster/${clusterId}/Analysis`,
path: `/api/cluster/${idCluster}/Stat`,
});
return result.body;
}