diff --git a/src/components/PageHeader.jsx b/src/components/PageHeader.jsx deleted file mode 100644 index 68c7731..0000000 --- a/src/components/PageHeader.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Layout, Button } from 'antd' -import { UserOutlined } from '@ant-design/icons' -import logo from '../images/logo_32.png' -import { Link } from "react-router-dom" -import WellTreeSelector from './WellTreeSelector' - -const { Header } = Layout - -export default function PageHeader({title='Мониторинг', wellsList}){ - const login = localStorage['login'] - - let handleLogout = () => { - localStorage.removeItem('login') - localStorage.removeItem('token') - } - - return( - - - - - - - {title} - - }> - ({login}) Выход - - - - - ) -}; \ No newline at end of file diff --git a/src/components/PageHeader.tsx b/src/components/PageHeader.tsx new file mode 100644 index 0000000..569c39e --- /dev/null +++ b/src/components/PageHeader.tsx @@ -0,0 +1,36 @@ +import { Layout, Button } from 'antd' +import { UserOutlined } from '@ant-design/icons' +import logo from '../images/logo_32.png' +import { Link } from 'react-router-dom' +import WellTreeSelector from './WellTreeSelector' +import { headerHeight } from '../utils' + +const { Header } = Layout + +const logoStyle = { height: headerHeight } + +const handleLogout = () => { + localStorage.removeItem('login') + localStorage.removeItem('token') +} + +type PageHeaderProps = { title?: string } + +export const PageHeader = ({ title = 'Мониторинг' }: PageHeaderProps) => ( + + + + + + + {title} + + }> + ({localStorage['login']}) Выход + + + + +) + +export default PageHeader diff --git a/src/components/PeriodPicker.tsx b/src/components/PeriodPicker.tsx index 3442d0e..88bbe6a 100644 --- a/src/components/PeriodPicker.tsx +++ b/src/components/PeriodPicker.tsx @@ -19,13 +19,11 @@ type PeriodPickerProps = { } export const PeriodPicker = ({ defaultValue = defaultPeriod, onChange }: PeriodPickerProps) => ( - onChange?.(Number(value))}> - {timePeriodCollection.map(period => ( - - {period.label} - - ))} - + onChange?.(Number(value))} + options={timePeriodCollection} + /> ) export default PeriodPicker diff --git a/src/components/Table/EditableCell.jsx b/src/components/Table/EditableCell.jsx deleted file mode 100644 index 84dae08..0000000 --- a/src/components/Table/EditableCell.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Form, Input} from "antd" - -export const EditableCell = ({ - editing, - record, - dataIndex, - input, - isRequired, - title, - formItemClass, - formItemRules, - children, - initialValue, -}) => { - - const inputNode = input ?? - const rules = formItemRules ?? [{ - required: isRequired, - message: `Please Input ${title}!`, - }] - - const editor = - {inputNode} - - - const tdStyle = editing - ? { padding:0 } - : null - - return ( - {editing ? editor: children} - ) -} \ No newline at end of file diff --git a/src/components/Table/EditableCell.tsx b/src/components/Table/EditableCell.tsx new file mode 100644 index 0000000..25168d3 --- /dev/null +++ b/src/components/Table/EditableCell.tsx @@ -0,0 +1,43 @@ +import { Form, Input } from 'antd' +import { NamePath, Rule } from 'rc-field-form/lib/interface' + +type EditableCellProps = { + editing?: boolean + dataIndex?: NamePath + input?: React.Component + isRequired?: boolean + title: string + formItemClass?: string + formItemRules?: Rule[] + children: React.ReactNode + initialValue: any +} + +export const EditableCell = ({ + editing, + dataIndex, + input, + isRequired, + title, + formItemClass, + formItemRules, + children, + initialValue, +}: EditableCellProps) => ( + + {!editing ? children : ( + + {input ?? } + + )} + +) diff --git a/src/components/Table/EditableTable.jsx b/src/components/Table/EditableTable.jsx index 568309a..68544be 100644 --- a/src/components/Table/EditableTable.jsx +++ b/src/components/Table/EditableTable.jsx @@ -14,13 +14,14 @@ export const tryAddKeys = (items) => { } export const EditableTable = ({ - columns, - dataSource, - onChange, // Метод вызывается со всем dataSource с измененными элементами после любого действия - onRowAdd, // Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается - onRowEdit,// Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается - onRowDelete,// Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается - ...otherTableProps}) => { + columns, + dataSource, + onChange, // Метод вызывается со всем dataSource с измененными элементами после любого действия + onRowAdd, // Метод вызывается с новой добавленной записью. Если метод не определен, то кнопка добавления строки не показывается + onRowEdit,// Метод вызывается с новой отредактированной записью. Если метод не поределен, то кнопка редактирования строки не показывается + onRowDelete,// Метод вызывается с удаленной записью. Если метод не поределен, то кнопка удаления строки не показывается + ...otherTableProps +}) => { const [form] = Form.useForm() const [data, setData] = useState(tryAddKeys(dataSource)) @@ -70,20 +71,20 @@ export const EditableTable = ({ if(item.key === newRowKeyValue) item.key = newRowKeyValue + newData.length - + const isAdding = editingKey === newRowKeyValue setEditingKey('') setData(newData) if (isAdding) try{ - onRowAdd(newItem) + onRowAdd(newItem) }catch(err){ console.log('callback onRowAdd fault:', err) } else try{ - onRowEdit(newItem) + onRowEdit(newItem) }catch(err){ console.log('callback onRowEdit fault:', err) } @@ -103,10 +104,10 @@ export const EditableTable = ({ const deleteRow = (record) =>{ const newData = [...data] const index = newData.findIndex((item) => record.key === item.key) - + newData.splice(index, 1) setData(newData) - + onRowDelete(record) if(onChange) @@ -115,7 +116,7 @@ export const EditableTable = ({ const operationColumn = { width: 82, - title: (!!onRowAdd) && } @@ -123,24 +124,24 @@ export const EditableTable = ({ dataIndex: 'operation', render: (_, record) => { const editable = isEditing(record) - return editable + return editable ?( - save(record)} icon={}/> - }/> - ) + ) :( - {onRowEdit&& edit(record)} icon={}/>} {onRowDelete&& deleteRow(record)}> }/> - } + } ) }, } @@ -149,14 +150,14 @@ export const EditableTable = ({ if (col.children) col.children = col.children.map(handleColumn) - if (!col.editable) - return col + if (!col.editable) + return col return { ...col, onCell: (record) => ({ - editing: isEditing(record), - record, + editing: isEditing(record), + record, dataIndex: col.dataIndex ?? col.key, key: col.key ?? col.dataIndex, input: col.input, @@ -171,7 +172,7 @@ export const EditableTable = ({ } const mergedColumns = [...columns.map(handleColumn), operationColumn] - + return ( ) -} \ No newline at end of file +} diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx index 1f2d4ea..8dff5ad 100644 --- a/src/components/Table/index.tsx +++ b/src/components/Table/index.tsx @@ -10,21 +10,19 @@ export { SelectFromDictionary } from './SelectFromDictionary' export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/ export const formatDate='YYYY.MM.DD HH:mm' -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) +export const makeNumericRender = (fixed?: number) => (value: any, row: object): ReactNode => { + let val = '-' + if (value && Number.isFinite(+value)) { + val = !!fixed + ? (+value).toFixed(fixed) : (+value).toPrecision(5) - } - - return ( - {val} - ) + } + + return ( + + {val} + + ) } export const makeNumericColumnOptions = (fixed?:number, sorterKey?:string ) => ({ diff --git a/src/images/logoSmaill.svg b/src/images/logoSmaill.svg new file mode 100644 index 0000000..a046adb --- /dev/null +++ b/src/images/logoSmaill.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Archive/index.jsx b/src/pages/Archive/index.jsx index e39a1c6..1f23d7c 100644 --- a/src/pages/Archive/index.jsx +++ b/src/pages/Archive/index.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ import moment from 'moment' import { DatePicker } from 'antd' import { useState, useEffect } from 'react' diff --git a/src/pages/TelemetryView/Setpoints.jsx b/src/pages/TelemetryView/Setpoints.jsx new file mode 100644 index 0000000..ad2701e --- /dev/null +++ b/src/pages/TelemetryView/Setpoints.jsx @@ -0,0 +1,127 @@ +import { Button, Input, Modal, Select } from 'antd' +import { useState } from 'react' +import { invokeWebApiWrapperAsync } from '../../components/factory' +import LoaderPortal from '../../components/LoaderPortal' +import PeriodPicker, { defaultPeriod } from '../../components/PeriodPicker' +import { EditableTable, makeNumericRender } from '../../components/Table' +import { SetpointsService } from '../../services/api' + +export const Setpoints = ({ idWell, ...other }) => { + const [isModalShown, setIsModalShown] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [isUploading, setIsUploading] = useState(false) + const [setpointNames, setSetpointNames] = useState([]) + const [setpoints, setSetpoints] = useState([]) + const [comment, setComment] = useState('') + const [expirePeriod, setExpirePeriod] = useState(defaultPeriod) + + const columns = [ + { + title: 'Наименование установки', + dataIndex: 'name', + editable: true, + isRequired: true, + width: 200, + input: , + render: (val) => setpointNames.find((name) => name.value === val)?.label + }, { + title: 'Значение', + dataIndex: 'value', + editable: true, + isRequired: true, + width: 125, + render: makeNumericRender(7), + align: 'right' + } + ] + + const onOpenClick = () => invokeWebApiWrapperAsync( + async () => { + const names = await SetpointsService.getSetpointsNamesByIdWell(idWell) + if (!names) throw Error('Setpoints not found') + setSetpointNames(names.map(spn => ({ + label: spn.displayName, + value: spn.name, + tooltip: spn.comment + }))) + setIsModalShown(true) + }, + setIsLoading, + `Не удалось загрузить список для скважины "${idWell}"` + ) + + const onModalOk = () => invokeWebApiWrapperAsync( + async () => { + // eslint-disable-next-line no-sequences + const setpointsObject = setpoints.reduce((obj, sp) => (obj[sp.name] = sp.value, obj), {}) + const request = { + uploadDate: new Date(), + obsolescenceSec: expirePeriod, + setpoints: setpointsObject, + comment: comment + } + await SetpointsService.insert(idWell, request) + setIsModalShown(false) + }, + setIsUploading, + `Не удалось отправить рекомендации по скважине "${idWell}"` + ) + + const onAdd = async (setpoint) => setSetpoints((prevSetpoints) => { + setpoint.key = Date.now() + prevSetpoints.push(setpoint) + return prevSetpoints + }) + + const onEdit = async (setpoint) => setSetpoints((prevSetpoints) => { + const idx = prevSetpoints.findIndex((val) => val.key === setpoint.key) + prevSetpoints[idx] = setpoint + return prevSetpoints + }) + + const onDelete = async (setpoint) => setSetpoints((prevSetpoints) => { + const idx = prevSetpoints.findIndex((val) => val.key === setpoint.key) + prevSetpoints.splice(idx, 1) + return prevSetpoints + }) + + return ( + + + Рекомендовать установки + + setIsModalShown(false)} + onOk={onModalOk} + okText={'Отправить'} + > + + + Период актуальности рекомендаций: + + + + Комментарий: + setComment(e.value)} + value={comment} + required + /> + + + + ) +} diff --git a/src/pages/TelemetryView/index.jsx b/src/pages/TelemetryView/index.jsx index 5d66e2d..529156e 100644 --- a/src/pages/TelemetryView/index.jsx +++ b/src/pages/TelemetryView/index.jsx @@ -6,6 +6,7 @@ import { CustomColumn } from './CustomColumn' import ActiveMessagesOnline from './ActiveMessagesOnline' import { ModeDisplay } from './ModeDisplay' import { UserOfWell } from './UserOfWells' +import { Setpoints } from './Setpoints' import LoaderPortal from '../../components/LoaderPortal' import { Grid, GridItem, Flex } from '../../components/Grid' @@ -380,6 +381,7 @@ export default function TelemetryView({ idWell }) { Завершено + diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..252ffa6 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,9 @@ +export type { RawDate } from './DateTimeUtils' +export { isRawDate } from './DateTimeUtils' + +export const headerHeight = 64 + +export const mainFrameSize = () => ({ + width: window.innerWidth, + height: window.innerHeight - headerHeight +})