diff --git a/package.json b/package.json index 576e207..13399cb 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "react_test": "react-scripts test", "eject": "react-scripts eject" }, - "proxy": "http://192.168.1.70:5000", + "proxy": "http://192.168.1.58:5000", "eslintConfig": { "extends": [ "react-app", diff --git a/src/components/ErrorFetch.js b/src/components/ErrorFetch.js new file mode 100644 index 0000000..5f4cdbd --- /dev/null +++ b/src/components/ErrorFetch.js @@ -0,0 +1,7 @@ +export class ErrorFetch extends Error { + constructor(status, message) { + super(message); + this.name = "ErrorFetch" + this.status = status + } +} \ No newline at end of file diff --git a/src/components/Table/index.ts b/src/components/Table/index.tsx similarity index 83% rename from src/components/Table/index.ts rename to src/components/Table/index.tsx index 8575a03..a4d6dc5 100644 --- a/src/components/Table/index.ts +++ b/src/components/Table/index.tsx @@ -1,4 +1,6 @@ import { ReactNode } from 'react' +import { makeNumericSorter, makeStringSorter} from './sorters' +export { makeDateSorter, makeNumericSorter, makeStringSorter} from './sorters' export { Table } from 'antd' export { EditableTable } from './EditableTable' export { DatePickerWrapper } from './DatePickerWrapper' @@ -7,10 +9,28 @@ export { SelectFromDictionary } from './SelectFromDictionary' export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/ export const formatDate='YYYY.MM.DD HH:mm' -export const numericColumnOptions = { +export const makeNumericRender = (fixed?:number) => (value: any, row: object): ReactNode => { + const placeholder = '-' + let val = placeholder + if((value !== null) || + (value !== undefined) || + Number.isNaN(value) || + !Number.isFinite(value)){ + val = !!fixed + ? (+value).toFixed(fixed) + : (+value).toPrecision(5) + } + + return (
+ {val} +
) +} + +export const makeNumericColumnOptions = (fixed?:number, sorterKey?:string ) => ({ editable: true, initialValue: 0, width:100, + sorter: sorterKey? makeNumericSorter(sorterKey) : null, formItemRules: [ { required: true, @@ -18,7 +38,8 @@ export const numericColumnOptions = { pattern: RegExpIsFloat, }, ], -}; + render: makeNumericRender(fixed), +}) /* other - объект с дополнительными свойствами колонки @@ -78,41 +99,6 @@ export const makeColumnsPlanFact = (title:string | ReactNode, key:string|string[ export const makeFilterTextMatch = (key: string | number) => (filterValue: string | number, dataItem: any) => dataItem[key] === filterValue -export const makeNumericSorter = (key: string) => (a: any, b: any) => a[key] - b[key] - -export const makeStringSorter = (key: string) => (a: any, b: any) => -{ - if(a == null && b == null) - return 1 - - if(a == null) - return 1 - - if(b == null) - return -1 - - let aValue = a[key] - let bValue = b[key] - - for (let i = 0; i < a.length; i++) { - if (isNaN(aValue.charCodeAt(i)) || (aValue.charCodeAt(i) > bValue.charCodeAt(i))) - return 1 - - if (aValue.charCodeAt(i) > bValue.charCodeAt(i)) - return -1 - } - return 0 -} - -export const makeDateSorter = (key: string) => (a: any, b: any) => { - const date = new Date(a[key]) - - if(Number.isNaN(date.getTime())) - throw new Error('Date column contains not date formatted string(s)') - - return date.getTime() - new Date(b[key]).getTime() -} - export const makeGroupColumn = (title: string, children: object[]) => ({ title: title, children: children, @@ -135,17 +121,6 @@ export const makeTextColumn = ( ...other }) -export const defaultNumericRender = (value: any, row: object) => { - const placeholder = '-' - if((value === null) || - (value === undefined) || - Number.isNaN(value) || - !Number.isFinite(value)) - return placeholder - - return (+value).toPrecision(5) -} - export const makeNumericColumn = (title: string, dataIndex: string, filters: object[], filterDelegate: (key: string | number) => any, renderDelegate: (_: any, row: object) => any, width: string, other?: columnPropsOther) => ({ @@ -156,7 +131,7 @@ export const makeNumericColumn = (title: string, dataIndex: string, onFilter: filterDelegate ? filterDelegate(dataIndex) : null, sorter: makeNumericSorter(dataIndex), width: width, - render: renderDelegate??defaultNumericRender, + render: renderDelegate??makeNumericRender(), align: 'right', ...other }) diff --git a/src/components/Table/sorters.ts b/src/components/Table/sorters.ts new file mode 100644 index 0000000..71732cb --- /dev/null +++ b/src/components/Table/sorters.ts @@ -0,0 +1,33 @@ +export const makeNumericSorter = (key: string) => (a: any, b: any) => a[key] - b[key]; + +export const makeStringSorter = (key: string) => (a: any, b: any) => { + if (a == null && b == null) + return 1; + + if (a == null) + return 1; + + if (b == null) + return -1; + + let aValue = a[key]; + let bValue = b[key]; + + for (let i = 0; i < a.length; i++) { + if (isNaN(aValue.charCodeAt(i)) || (aValue.charCodeAt(i) > bValue.charCodeAt(i))) + return 1; + + if (aValue.charCodeAt(i) > bValue.charCodeAt(i)) + return -1; + } + return 0; +}; + +export const makeDateSorter = (key: string) => (a: any, b: any) => { + const date = new Date(a[key]); + + if (Number.isNaN(date.getTime())) + throw new Error('Date column contains not date formatted string(s)'); + + return date.getTime() - new Date(b[key]).getTime(); +}; diff --git a/src/components/UploadForm.jsx b/src/components/UploadForm.jsx index 95a3055..47be967 100644 --- a/src/components/UploadForm.jsx +++ b/src/components/UploadForm.jsx @@ -2,22 +2,38 @@ import { Upload, Button } from 'antd' import { UploadOutlined } from '@ant-design/icons' import { useState } from 'react' import { upload } from './factory' +import { ErrorFetch } from './ErrorFetch' -export default function UploadForm({url, accept, onUploadStart, onUploadComplete, onUploadError}) { +export const UploadForm = ({url, accept, style, formData, onUploadStart, onUploadSuccess, onUploadComplete, onUploadError}) => { const [fileList, setfileList] = useState([]) - const handleFileSend = async (values) => { + const handleFileSend = async () => { if(onUploadStart) onUploadStart() try { - const formData = new FormData() + const formDataLocal = new FormData() fileList.forEach((val) => { - formData.append("files", val.originFileObj); - }); - await upload(url, formData) + formDataLocal.append("files", val.originFileObj) + }) + + if(formData) + for(var propName in formData) + formDataLocal.append(propName, formData[propName]) + + const response = await upload(url, formDataLocal) + if(!response.ok) + { + const errorText = await response.text() + const error = new ErrorFetch(response.status, errorText) + throw error + } + else{ + if(onUploadSuccess) + onUploadSuccess() + } } catch(error) { if(onUploadError) - onUploadError(error) + onUploadError(error) } finally { setfileList([]) if(onUploadComplete) @@ -27,7 +43,7 @@ export default function UploadForm({url, accept, onUploadStart, onUploadComplete const isSendButtonEnabled = fileList.length > 0 return( -
+
{ Фамилия: - + {user?.surname} diff --git a/src/components/factory.ts b/src/components/factory.ts index ca09446..6402027 100644 --- a/src/components/factory.ts +++ b/src/components/factory.ts @@ -81,13 +81,14 @@ export const download = async (url:string, fileName?:string) => { } export const upload = async (url:string, formData: FormData) => { - await fetch(url, { + let response = await fetch(url, { headers: { Authorization: 'Bearer ' + localStorage['token'] }, method: 'Post', body: formData, }) + return response } export const downloadFile = async (fileInfo: FileInfoDto) => { diff --git a/src/pages/Archive.jsx b/src/pages/Archive.jsx index 3a9f28b..cb75ad5 100644 --- a/src/pages/Archive.jsx +++ b/src/pages/Archive.jsx @@ -5,7 +5,7 @@ import { Row, Col, Tooltip} from 'antd' -import { DataService } from '../services/api' +import { TelemetryDataSaubService } from '../services/api' import {generateUUID} from '../services/UidGenerator' import { ArchiveColumn } from '../components/ArchiveColumn' import moment from 'moment' @@ -94,7 +94,7 @@ export default function Archive({idWell}) { let startDate = rangeDate[0].toISOString() setLoader(true) - DataService.getData(idWell, startDate, interval, 2048) + TelemetryDataSaubService.getData(idWell, startDate, interval, 2048) .then(handleReceiveDataSaub) .catch(error => { notify(`Не удалось загрузить данные по скважине (${idWell}) c ${rangeDate[0]} по ${rangeDate[1]}`, 'error') diff --git a/src/pages/Cluster/ClusterSections.jsx b/src/pages/Cluster/ClusterSections.jsx index b87ecd6..f3b862b 100644 --- a/src/pages/Cluster/ClusterSections.jsx +++ b/src/pages/Cluster/ClusterSections.jsx @@ -7,7 +7,7 @@ import { makeNumericColumnPlanFact } from "../../components/Table"; import { invokeWebApiWrapperAsync } from '../../components/factory'; -import ChartDepthToDay from '../../components/charts/ChartDepthToDay'; +import ChartTvD from '../WellOperations/ChartTvD'; import WellOperationsTable from './WellOperationsTable' import { calcAndUpdateStatsBySections, @@ -249,10 +249,10 @@ export default function ClusterSections({ clusterData }) { width={1500} footer={null} > - + dataPredict={tvdDataForecast} /> { width={1500} footer={null} > - diff --git a/src/pages/Cluster/functions.jsx b/src/pages/Cluster/functions.jsx index 557e867..a50358b 100644 --- a/src/pages/Cluster/functions.jsx +++ b/src/pages/Cluster/functions.jsx @@ -6,17 +6,24 @@ const minPrefix = "isMin" export const getOperations = async (idWell) => { const ops = await WellOperationStatService.getTvd(idWell); - const planData = ops.map(el => { - return {key: el.plan?.id, depth: el.plan?.wellDepth, date: el.plan?.startDate} - }).filter(el => el.key) + const convert = wellOperationDto => + ({ + key: wellOperationDto?.id, + depth: wellOperationDto?.depthStart, + date: wellOperationDto?.dateStart + }) - const factData = ops.map(el => { - return {key: el.fact?.id, depth: el.fact?.wellDepth, date: el.fact?.startDate} - }).filter(el => el.key) + const planData = ops + .map(item => convert(item.plan)) + .filter(el => el.key) - const predictData = ops.map(el => { - return {key: el.predict?.id, depth: el.predict?.wellDepth, date: el.predict?.startDate} - }).filter(el => el.key) + const factData = ops + .map(item => convert(item.fact)) + .filter(el => el.key) + + const predictData = ops + .map(item => convert(item.predict)) + .filter(el => el.key) return { operations: ops, plan: planData, fact: factData, predict: predictData } } diff --git a/src/pages/Documents/DocumentsTemplate.jsx b/src/pages/Documents/DocumentsTemplate.jsx index 4f8c6af..6409392 100644 --- a/src/pages/Documents/DocumentsTemplate.jsx +++ b/src/pages/Documents/DocumentsTemplate.jsx @@ -8,7 +8,7 @@ import { formatBytes, } from "../../components/factory" import { EditableTable, makePaginationObject } from "../../components/Table" -import UploadForm from "../../components/UploadForm" +import {UploadForm} from "../../components/UploadForm" import LoaderPortal from "../../components/LoaderPortal" import {UserView} from '../../components/UserView' import {CompanyView} from '../../components/CompanyView' diff --git a/src/pages/Measure/MeasureTable.jsx b/src/pages/Measure/MeasureTable.jsx index 7aab0f0..4831ec6 100644 --- a/src/pages/Measure/MeasureTable.jsx +++ b/src/pages/Measure/MeasureTable.jsx @@ -1,54 +1,198 @@ import { useState, useEffect } from 'react' -import { Table, Button, Modal } from 'antd' -import { HourglassOutlined } from '@ant-design/icons' +import { Button, Form, Input, Popconfirm, Timeline } from 'antd' +import moment from 'moment' +import { CheckSquareOutlined, + EditOutlined, + SaveOutlined, + PlusOutlined, + CloseCircleOutlined, + DeleteOutlined } from '@ant-design/icons' +import { View } from './View' import LoaderPortal from '../../components/LoaderPortal' -import { invokeWebApiWrapperAsync } from '../../components/factory' import { MeasureService } from '../../services/api' -import { Editor } from './Editor' +import '../../styles/index.css' +import '../../styles/measure.css' -export const MeasureTable = ({idWell, idCategory, title, columns}) => { - const [showLoader, setShowLoader] = useState(false) - const [showEditor, setShowEditor] = useState(false) - const [lastData, setLastData] = useState({}) +const format='YYYY.MM.DD HH:mm' - const update = ()=>invokeWebApiWrapperAsync(async()=>{ - const data = await MeasureService.getLast(idWell, idCategory) - setLastData(data) +export const MeasureTable = ({idWell, idCategory, title, columns, values, updateMeasuresFunc}) => { + + const [showLoader, setShowLoader] = useState(false); + const [displayedValues, setDisplayedValues] = useState({}); + const [editingColumns, setEditingColumns] = useState(columns); + const [isTableEditing, setIsTableEditing] = useState(false); + const [editingActionName, setEditingActionName] = useState(''); + + const [measuresForm] = Form.useForm(); + + const createEditingColumns = (cols, renderDelegate) => + cols.map(col => + ({ render: renderDelegate, + ...col + }) + ) + + useEffect(() => { + const defaultValuesToDisplay = values[values.length-1] + + setDisplayedValues(defaultValuesToDisplay) + }, [values]) + + useEffect(() => { + let switchableColumns = [] + + isTableEditing + ? switchableColumns = createEditingColumns(columns, () => ) + : switchableColumns = createEditingColumns(columns, null) + + if(editingActionName === 'edit') + measuresForm.setFieldsValue(displayedValues?.data); + else if(editingActionName === 'add') + measuresForm.resetFields() + + setEditingColumns(switchableColumns) + }, [isTableEditing, columns, editingActionName, displayedValues?.data, measuresForm]) + + const markMeasuresAsDeleted = async () => { + setShowLoader(true) + await MeasureService.markAsDelete(idWell, displayedValues.id) + updateMeasuresFunc() + setShowLoader(false) } - , setShowLoader - , "не удалось загрузить") - useEffect(update, [idWell, idCategory]) + const checkIsDataDefault = () => + displayedValues?.isDefaultData ? true : false - const timestamp = lastData ? new Date(lastData?.timestamp).toLocaleString() : '-' - - return -

{title}

- Дата: {timestamp} -   - - - setShowEditor(false)} - onCancel={() => setShowEditor(false)} - width="95%" - footer={null} + const crudButtons = +
+ + + markMeasuresAsDeleted()} + disabled={checkIsDataDefault()} + > + + +
+ + const confirmButtons = +
+
+ + +
+
+ + let handleSubmitMeasuresForm = async (formData) => { + measuresForm.validateFields() + + const measureParams = { + idWell: idWell, + idCategory: idCategory, + timestamp: new Date().toISOString(), + data: formData + } + + setShowLoader(true) + + if(editingActionName === 'add') { + await MeasureService.insert(idWell, measureParams) + } else if (editingActionName === 'edit') { + measureParams.id = displayedValues.id + measureParams.timestamp = displayedValues.timestamp + await MeasureService.update(idWell, measureParams) + } + + setIsTableEditing(false) + updateMeasuresFunc() + setShowLoader(false) + } + + return <> +   +

{title}

+   +
+
+
+ {isTableEditing + ? confirmButtons + : crudButtons + } +
+ +
+ + {values.map((item, index) => + setDisplayedValues(item)} + dot={item?.id === displayedValues?.id + ? + : null} + > + + {item.timestamp ? moment.utc(item.timestamp).local().format(format) : 'Нет данных'} + + + )} + +
+
+
+ +
+ + +
+
+
+ } \ No newline at end of file diff --git a/src/pages/Measure/MeasureTable2.jsx b/src/pages/Measure/MeasureTableOld.jsx similarity index 53% rename from src/pages/Measure/MeasureTable2.jsx rename to src/pages/Measure/MeasureTableOld.jsx index 621e711..7aab0f0 100644 --- a/src/pages/Measure/MeasureTable2.jsx +++ b/src/pages/Measure/MeasureTableOld.jsx @@ -1,48 +1,40 @@ import { useState, useEffect } from 'react' -import { Button, Modal, Timeline } from 'antd' -import moment from 'moment' +import { Table, Button, Modal } from 'antd' import { HourglassOutlined } from '@ant-design/icons' import LoaderPortal from '../../components/LoaderPortal' import { invokeWebApiWrapperAsync } from '../../components/factory' import { MeasureService } from '../../services/api' import { Editor } from './Editor' -import TimelineItem from 'antd/lib/timeline/TimelineItem' -//import { View } from './View' -const format='YYYY.MM.DD HH:mm' - -export const MeasureTable2 = ({idWell, idCategory, title, columns}) => { +export const MeasureTable = ({idWell, idCategory, title, columns}) => { const [showLoader, setShowLoader] = useState(false) const [showEditor, setShowEditor] = useState(false) - const [history, setHistory] = useState([]) + const [lastData, setLastData] = useState({}) - const update = () => invokeWebApiWrapperAsync(async()=>{ - const data = await MeasureService.getHisory(idWell, idCategory) - const story = data?.map( i=> ({ - id: i.id, - idWell: i.idWell, - idCategory: i.idCategory, - timestamp: moment.utc(i.timestamp).local().format(format), - ...i.data})) - setHistory(story??[]) + const update = ()=>invokeWebApiWrapperAsync(async()=>{ + const data = await MeasureService.getLast(idWell, idCategory) + setLastData(data) } , setShowLoader , "не удалось загрузить") useEffect(update, [idWell, idCategory]) + const timestamp = lastData ? new Date(lastData?.timestamp).toLocaleString() : '-' + return

{title}

+ Дата: {timestamp}   - - {history.map(item=>{item.timestamp})} - - {/* */} + icon={}>История +
{ + if(column.render) { + return ( + + {column.render(itm[column.dataIndex])} + + ) + } + + return

{itm[column.dataIndex]}

+} export const View = ({columns, item}) => { if (!item || !columns?.length) - return + return const colsCount = 3 const viewItems = columns.map( (column, i) => { const row = Math.floor(i / colsCount) + 1 const colb = i % colsCount + return <> + style={{border:'1px solid lightgrey'}} + > {column.title} - + - {column.render ? column.render(item[column.dataIndex]) : item[column.dataIndex]} + style={{border:'1px solid lightgrey', + justifyContent:'right', + marginRight:'16px', + fontWeight:'bold', + textAlign:'right', + padding: 0}} + > + {renderSwitchableColumn(column, item)} }) - return - {viewItems} - + return <> + + {viewItems} + + } \ No newline at end of file diff --git a/src/pages/Measure/columnsCommon.js b/src/pages/Measure/columnsCommon.js index 7d458b5..5e7a616 100644 --- a/src/pages/Measure/columnsCommon.js +++ b/src/pages/Measure/columnsCommon.js @@ -42,4 +42,10 @@ export const numericColumnOptions = { export const textColumnOptions = { editable:true, input: