Добавлена таблица собирательной инклинометрии

This commit is contained in:
goodmice 2021-11-17 14:08:13 +05:00
parent 1ebaf3c185
commit 252cdfe7dd
5 changed files with 313 additions and 262 deletions

View File

@ -0,0 +1,50 @@
import React, { useEffect, useState } from 'react'
import { Modal } from 'antd'
import { Table } from '../../components/Table'
import { formatDate } from './MeasureTable'
import { v } from './columnsCommon'
export const InclinometryTable = React.memo(({ group, visible, onClose }) => {
const [tableColumns, setTableColumns] = useState([])
const [tableData, setTableData] = useState([])
useEffect(() => {
setTableColumns([{
title: 'Дата',
key: 'date',
dataIndex: 'date',
render: (item) => formatDate(item),
fixed: 'left',
width: '8rem',
},
...(group?.columns?.map((column) => ({...column, title: v(column.title)})) ?? [])
])
}, [group?.columns])
useEffect(() => {
setTableData(group?.values?.map(row => ({ date: row.timestamp, ...row.data })))
}, [group?.values])
return !group?.columns ? null : (
<Modal
title={group?.title}
centered
visible={visible}
onCancel={onClose}
width={1900}
footer={null}
>
<Table
dataSource={tableData}
columns={tableColumns}
scroll={{
y: 400,
x: 1300
}}
bordered
/>
</Modal>
)
})
export default InclinometryTable

View File

@ -1,140 +1,84 @@
import { useState, useEffect } from 'react'
import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
import moment from 'moment'
import { CheckSquareOutlined,
import {
CheckSquareOutlined,
EditOutlined,
SaveOutlined,
PlusOutlined,
CloseCircleOutlined,
DeleteOutlined } from '@ant-design/icons'
DeleteOutlined
} from '@ant-design/icons'
import { View } from './View'
import LoaderPortal from '../../components/LoaderPortal'
import { MeasureService } from '../../services/api'
import '../../styles/index.css'
import '../../styles/measure.css'
import { invokeWebApiWrapperAsync } from '../../components/factory'
const format='YYYY.MM.DD HH:mm'
const dateFormat = 'YYYY.MM.DD HH:mm'
export const formatDate = (date) => date ? moment.utc(date).local().format(dateFormat) : 'Нет данных'
export const MeasureTable = ({idWell, idCategory, title, columns, values, updateMeasuresFunc}) => {
export const MeasureTable = ({idWell, group, updateMeasuresFunc, additionalButtons}) => {
const [showLoader, setShowLoader] = useState(false)
const [displayedValues, setDisplayedValues] = useState({})
const [editingColumns, setEditingColumns] = useState(group.columns)
const [isTableEditing, setIsTableEditing] = useState(false)
const [editingActionName, setEditingActionName] = useState('')
const [data, setData] = useState([])
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 [measuresForm] = Form.useForm()
const createEditingColumns = (cols, renderDelegate) =>
cols.map(col =>
({ render: renderDelegate,
...col
})
cols.map(col => ({ render: renderDelegate, ...col }))
useEffect(() => {
const data = [group.defaultValue].concat(group.values ?? [])
setData(data)
setDisplayedValues(data.at(-1))
}, [group.defaultValue, group.values])
useEffect(() => {
const switchableColumns = createEditingColumns(
group.columns,
isTableEditing ? () => <Input className={'w-100 measure-input'} /> : null
)
useEffect(() => {
const defaultValuesToDisplay = values[values.length-1]
setDisplayedValues(defaultValuesToDisplay)
}, [values])
useEffect(() => {
let switchableColumns = []
isTableEditing
? switchableColumns = createEditingColumns(columns, () => <Input className='w-100 measure-input' />)
: switchableColumns = createEditingColumns(columns, null)
if(editingActionName === 'edit')
measuresForm.setFieldsValue(displayedValues?.data);
measuresForm.setFieldsValue(displayedValues?.data)
else if(editingActionName === 'add')
measuresForm.resetFields()
setEditingColumns(switchableColumns)
}, [isTableEditing, columns, editingActionName, displayedValues?.data, measuresForm])
}, [isTableEditing, group.columns, editingActionName, displayedValues?.data, measuresForm])
const markMeasuresAsDeleted = async () => {
setShowLoader(true)
const markMeasuresAsDeleted = async () => await invokeWebApiWrapperAsync(
async () => {
await MeasureService.markAsDelete(idWell, displayedValues.id)
updateMeasuresFunc()
setShowLoader(false)
},
setShowLoader,
`Не удалось удалить запись ${displayedValues.id} для скважины "${idWell}"`
)
const isDataDefault = () => !!displayedValues?.isDefaultData
const editTable = (action) => {
setEditingActionName(action)
setIsTableEditing(true)
}
const checkIsDataDefault = () =>
displayedValues?.isDefaultData ? true : false
const crudButtons =
<div className='w-300px mt-8px'>
<Button
key='add'
className='w-33'
onClick={() => {
setEditingActionName('add')
setIsTableEditing(true)
}}
>
<PlusOutlined />
</Button>
<Button
key='edit'
className='w-33'
onClick={() => {
setEditingActionName('edit')
setIsTableEditing(true)
}}
disabled={checkIsDataDefault()}
>
<EditOutlined />
</Button>
<Popconfirm
title="Удалить данные?"
onConfirm={() => markMeasuresAsDeleted()}
disabled={checkIsDataDefault()}
>
<Button
key='delete'
onClick={() => {
setEditingActionName('delete')
}}
disabled={checkIsDataDefault()}
>
<DeleteOutlined style={{margin:'auto 28px'}}/>
</Button>
</Popconfirm>
</div>
const confirmButtons =
<div className='w-300px mt-8px'>
<div className='d-flex'>
<Button
key='confirm'
className='w-50'
onClick={() => { measuresForm.submit() }}
>
<SaveOutlined />
</Button>
<Button
key='decline'
className='w-50'
onClick={()=> setIsTableEditing(false)}
>
<CloseCircleOutlined />
</Button>
</div>
</div>
let handleSubmitMeasuresForm = async (formData) => {
const handleSubmitMeasuresForm = async (formData) => await invokeWebApiWrapperAsync(
async () => {
measuresForm.validateFields()
const measureParams = {
idWell: idWell,
idCategory: idCategory,
idCategory: group.idCategory,
timestamp: new Date().toISOString(),
data: formData
}
setShowLoader(true)
if(editingActionName === 'add') {
await MeasureService.insert(idWell, measureParams)
} else if (editingActionName === 'edit') {
@ -145,54 +89,73 @@ export const MeasureTable = ({idWell, idCategory, title, columns, values, update
setIsTableEditing(false)
updateMeasuresFunc()
setShowLoader(false)
}
},
setShowLoader,
`Не удалось добавить/изменить запись для скаважины "${idWell}"`
)
return <>
&nbsp;
<h2>{title}</h2>
&nbsp;
<div className='d-flex'>
<div className='flex-direction-column'>
<div className='measure-buttons-container'>
{isTableEditing
? confirmButtons
: crudButtons
}
return (
<>
&nbsp;<h2>{group.title}</h2>&nbsp;
<div className={'d-flex'}>
<div className={'flex-direction-column'}>
<div className={'measure-buttons-container'}>
<div className={'w-300px mt-8px d-flex'} style={{ alignItems: 'stretch' }}>
{ isTableEditing ? (
<>
<Button key={'confirm'} className={'flex-1'} onClick={() => measuresForm.submit()}>
<SaveOutlined />
</Button>
<Button key={'decline'} className={'flex-1'} onClick={() => setIsTableEditing(false)}>
<CloseCircleOutlined />
</Button>
</>
) : (
<>
<Button key={'add'} className={'flex-1'} onClick={() => editTable('add')}>
<PlusOutlined />
</Button>
<Button key={'edit'} className={'flex-1'} onClick={() => editTable('edit')} disabled={isDataDefault()}>
<EditOutlined />
</Button>
<Popconfirm style={{flex: '1'}} title={'Удалить данные?'} onConfirm={markMeasuresAsDeleted} disabled={isDataDefault()}>
<Button key={'delete'} onClick={() => setEditingActionName('delete')} disabled={isDataDefault()} >
<DeleteOutlined style={{ margin:'auto 28px' }}/>
</Button>
</Popconfirm>
</>
)}
{additionalButtons?.(isTableEditing)}
</div>
</div>
<div className='measure-dates mt-20px'>
<Timeline className='mt-12px ml-10px'>
{values.map((item, index) =>
<div className={'measure-dates mt-20px'}>
<Timeline className={'mt-12px ml-10px'}>
{data.map((item, index) =>
<Timeline.Item
key={index}
className='measure-button'
className={'measure-button'}
onClick={() => setDisplayedValues(item)}
dot={item?.id === displayedValues?.id
? <CheckSquareOutlined className="timeline-clock-icon" />
: null}
dot={item?.id !== displayedValues?.id ? null :
<CheckSquareOutlined className={'timeline-clock-icon'} />
}
>
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}>
{item.timestamp ? moment.utc(item.timestamp).local().format(format) : 'Нет данных'}
{formatDate(item.timestamp)}
</span>
</Timeline.Item>
)}
</Timeline>
</div>
</div>
<div className='w-100'>
<div className={'w-100'}>
<LoaderPortal show={showLoader}>
<Form
form={measuresForm}
onFinish={handleSubmitMeasuresForm}
>
<View
item={displayedValues?.data ?? {}}
columns={editingColumns}
/>
<Form form={measuresForm} onFinish={handleSubmitMeasuresForm}>
<View item={displayedValues?.data ?? {}} columns={editingColumns} />
</Form>
</LoaderPortal>
</div>
</div>
</>
)
}

View File

@ -1,62 +1,61 @@
import { Empty, Form } from 'antd';
import React from 'react'
import { Empty, Form } from 'antd'
import { Grid, GridItem } from '../../components/Grid'
import '../../styles/index.css'
const renderSwitchableColumn = (column, itm) => {
if(column.render) {
return (
<Form.Item
key={column.dataIndex}
name={column.dataIndex}
style={{margin: 0}}
//rules={column.formItemRules}
>
{column.render(itm[column.dataIndex])}
</Form.Item>
)
const colsCount = 3
const headerCellStyle = {
border:'1px solid lightgrey'
}
return <p key={column.title} className='m-5px'>{itm[column.dataIndex]}</p>
const valueCellStyle = {
border:'1px solid lightgrey',
justifyContent:'right',
marginRight:'16px',
fontWeight:'bold',
textAlign:'right',
padding: 0
}
export const View = ({columns, item}) => {
export const View = React.memo(({columns, item}) => {
if (!item || !columns?.length)
return <Empty key='empty' image={Empty.PRESENTED_IMAGE_SIMPLE}/>
const colsCount = 3
const viewItems = columns.map( (column, i) => {
const row = Math.floor(i / colsCount) + 1
const colb = i % colsCount
return <>
return (
<Grid>
{columns.map((column, i) => (
<>
<GridItem
key={column.dataIndex}
row={row}
col={colb*2 + 1}
style={{border:'1px solid lightgrey'}}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 1}
style={headerCellStyle}
>
{column.title}
</GridItem>
<GridItem
key={column.title}
row={row}
col={colb*2 + 2}
style={{border:'1px solid lightgrey',
justifyContent:'right',
marginRight:'16px',
fontWeight:'bold',
textAlign:'right',
padding: 0}}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 2}
style={valueCellStyle}
>
{renderSwitchableColumn(column, item)}
{column.render ? (
<Form.Item
key={column.dataIndex}
name={column.dataIndex}
style={{ margin: 0 }}
//rules={column.formItemRules}
>
{column.render(item[column.dataIndex])}
</Form.Item>
) : (
<p key={column.title} className='m-5px'>{item[column.dataIndex]}</p>
)}
</GridItem>
</>
})
return <>
<Grid>
{viewItems}
))}
</Grid>
</>
}
)
})

View File

@ -1,63 +1,98 @@
import { useState, useEffect } from 'react'
import { columnsMudDiagram} from './mudDiagramData'
import { mudDiagramDefaultData} from './mudDiagramData'
import { columnsDrillingFluid} from './drillingFluidData'
import { drillingFluidDefaultData} from './drillingFluidData'
import { columnsNnb } from './nnbData'
import { nnbDefaultData } from './nnbData'
import { columnsNnb, nnbDefaultData } from './nnbData'
import { columnsMudDiagram, mudDiagramDefaultData } from './mudDiagramData'
import { columnsDrillingFluid, drillingFluidDefaultData } from './drillingFluidData'
import { invokeWebApiWrapperAsync } from '../../components/factory'
import { MeasureService } from '../../services/api'
import LoaderPortal from '../../components/LoaderPortal'
import { MeasureTable } from './MeasureTable'
import InclinometryTable from './InclinometryTable'
import { Button } from 'antd'
import { TableOutlined } from '@ant-design/icons'
const defaultData = [
{
idCategory: 1,
title: 'Замер бурового раствора',
columns: columnsDrillingFluid,
values: [drillingFluidDefaultData],
defaultValue: drillingFluidDefaultData,
},
{
idCategory: 2,
title: 'Шламограмма',
columns: columnsMudDiagram,
values: [mudDiagramDefaultData],
defaultValue: mudDiagramDefaultData,
},
{
idCategory: 3,
title: 'ННБ',
columns: columnsNnb,
values: [nnbDefaultData],
defaultValue: nnbDefaultData,
}
]
export default function Measure({idWell}){
const [showLoader, setShowLoader] = useState(false)
const [fluidValues, setFluidValues] = useState([])
const [mudValues, setMudValues] = useState([])
const [nnbValues, setNnbValues] = useState([])
const [isMeasuresUpdating, setIsMeasuresUpdating] = useState(false)
const [data, setData] = useState(defaultData)
const [tableIdx, setTableIdx] = useState(-1)
const updateCurrentValues = () => invokeWebApiWrapperAsync(async()=>{
const updateCurrentValues = () => invokeWebApiWrapperAsync(
async () => {
const measures = await MeasureService.getHisory(idWell)
setIsMeasuresUpdating(false)
const fluids = measures.filter(el => el.idCategory === 1)
setFluidValues(fluids.length ? fluids : [drillingFluidDefaultData])
const muds = measures.filter(el => el.idCategory === 2)
setMudValues(muds.length ? muds : [mudDiagramDefaultData])
const nnbs = measures.filter(el => el.idCategory === 3)
setNnbValues(nnbs.length ? nnbs : [nnbDefaultData])
}
,setShowLoader
,`Не удалось загрузить последние данные по скважине ${idWell}`)
setData(prevData => {
prevData.forEach(el => el.values = [])
measures.forEach(el => {
const idx = prevData.findIndex(group => el.idCategory === group.idCategory)
if (idx >= 0)
prevData[idx].values.push(el)
})
return prevData
})
},
setShowLoader,
`Не удалось загрузить последние данные по скважине ${idWell}`
)
useEffect(() => {
setData(prevData => {
data[2].additionalButtons = (group, idx) => (isEditing) => isEditing ? null : (
<Button
key={'table'}
className={'flex-1'}
onClick={() => setTableIdx(idx)}
disabled={!group.values || group.values.length <= 0}
>
<TableOutlined />
</Button>
)
return prevData
})
}, [])
useEffect(updateCurrentValues, [idWell, isMeasuresUpdating])
return <>
<LoaderPortal show={showLoader}>
{data.map((group, idx) => (
<MeasureTable
key={idx}
idWell={idWell}
idCategory={1}
title='Замер бурового раствора'
columns={columnsDrillingFluid}
values={fluidValues}
group={group}
updateMeasuresFunc={() => setIsMeasuresUpdating(true)}
additionalButtons={group.additionalButtons?.(group, idx)}
/>
<MeasureTable
idWell={idWell}
idCategory={2}
title='Шламограмма'
columns={columnsMudDiagram}
values={mudValues}
updateMeasuresFunc = {() => setIsMeasuresUpdating(true)}
/>
<MeasureTable
idWell={idWell}
idCategory={3}
title='ННБ'
columns={columnsNnb}
values={nnbValues}
updateMeasuresFunc = {() => setIsMeasuresUpdating(true)}
))}
<InclinometryTable
visible={tableIdx >= 0}
onClose={() => setTableIdx(-1)}
group={data[tableIdx]}
/>
</LoaderPortal>
</>

View File

@ -23,6 +23,10 @@ body {
display: none;
}
.flex-1 {
flex: 1;
}
.w-15 {
width: 15%
}