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

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 { useState, useEffect } from 'react'
import { Button, Form, Input, Popconfirm, Timeline } from 'antd' import { Button, Form, Input, Popconfirm, Timeline } from 'antd'
import moment from 'moment' import moment from 'moment'
import { CheckSquareOutlined, import {
CheckSquareOutlined,
EditOutlined, EditOutlined,
SaveOutlined, SaveOutlined,
PlusOutlined, PlusOutlined,
CloseCircleOutlined, CloseCircleOutlined,
DeleteOutlined } from '@ant-design/icons' DeleteOutlined
} from '@ant-design/icons'
import { View } from './View' import { View } from './View'
import LoaderPortal from '../../components/LoaderPortal' import LoaderPortal from '../../components/LoaderPortal'
import { MeasureService } from '../../services/api' import { MeasureService } from '../../services/api'
import '../../styles/index.css' import '../../styles/index.css'
import '../../styles/measure.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 [measuresForm] = Form.useForm()
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) => const createEditingColumns = (cols, renderDelegate) =>
cols.map(col => cols.map(col => ({ render: renderDelegate, ...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') if(editingActionName === 'edit')
measuresForm.setFieldsValue(displayedValues?.data); measuresForm.setFieldsValue(displayedValues?.data)
else if(editingActionName === 'add') else if(editingActionName === 'add')
measuresForm.resetFields() measuresForm.resetFields()
setEditingColumns(switchableColumns) setEditingColumns(switchableColumns)
}, [isTableEditing, columns, editingActionName, displayedValues?.data, measuresForm]) }, [isTableEditing, group.columns, editingActionName, displayedValues?.data, measuresForm])
const markMeasuresAsDeleted = async () => { const markMeasuresAsDeleted = async () => await invokeWebApiWrapperAsync(
setShowLoader(true) async () => {
await MeasureService.markAsDelete(idWell, displayedValues.id) await MeasureService.markAsDelete(idWell, displayedValues.id)
updateMeasuresFunc() updateMeasuresFunc()
setShowLoader(false) },
setShowLoader,
`Не удалось удалить запись ${displayedValues.id} для скважины "${idWell}"`
)
const isDataDefault = () => !!displayedValues?.isDefaultData
const editTable = (action) => {
setEditingActionName(action)
setIsTableEditing(true)
} }
const checkIsDataDefault = () => const handleSubmitMeasuresForm = async (formData) => await invokeWebApiWrapperAsync(
displayedValues?.isDefaultData ? true : false async () => {
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) => {
measuresForm.validateFields() measuresForm.validateFields()
const measureParams = { const measureParams = {
idWell: idWell, idWell: idWell,
idCategory: idCategory, idCategory: group.idCategory,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
data: formData data: formData
} }
setShowLoader(true)
if(editingActionName === 'add') { if(editingActionName === 'add') {
await MeasureService.insert(idWell, measureParams) await MeasureService.insert(idWell, measureParams)
} else if (editingActionName === 'edit') { } else if (editingActionName === 'edit') {
@ -145,54 +89,73 @@ export const MeasureTable = ({idWell, idCategory, title, columns, values, update
setIsTableEditing(false) setIsTableEditing(false)
updateMeasuresFunc() updateMeasuresFunc()
setShowLoader(false) },
} setShowLoader,
`Не удалось добавить/изменить запись для скаважины "${idWell}"`
)
return <> return (
&nbsp; <>
<h2>{title}</h2> &nbsp;<h2>{group.title}</h2>&nbsp;
&nbsp; <div className={'d-flex'}>
<div className='d-flex'> <div className={'flex-direction-column'}>
<div className='flex-direction-column'> <div className={'measure-buttons-container'}>
<div className='measure-buttons-container'> <div className={'w-300px mt-8px d-flex'} style={{ alignItems: 'stretch' }}>
{isTableEditing { isTableEditing ? (
? confirmButtons <>
: crudButtons <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>
<div className='measure-dates mt-20px'> <div className={'measure-dates mt-20px'}>
<Timeline className='mt-12px ml-10px'> <Timeline className={'mt-12px ml-10px'}>
{values.map((item, index) => {data.map((item, index) =>
<Timeline.Item <Timeline.Item
key={index} key={index}
className='measure-button' className={'measure-button'}
onClick={() => setDisplayedValues(item)} onClick={() => setDisplayedValues(item)}
dot={item?.id === displayedValues?.id dot={item?.id !== displayedValues?.id ? null :
? <CheckSquareOutlined className="timeline-clock-icon" /> <CheckSquareOutlined className={'timeline-clock-icon'} />
: null} }
> >
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}> <span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}>
{item.timestamp ? moment.utc(item.timestamp).local().format(format) : 'Нет данных'} {formatDate(item.timestamp)}
</span> </span>
</Timeline.Item> </Timeline.Item>
)} )}
</Timeline> </Timeline>
</div> </div>
</div> </div>
<div className='w-100'> <div className={'w-100'}>
<LoaderPortal show={showLoader}> <LoaderPortal show={showLoader}>
<Form <Form form={measuresForm} onFinish={handleSubmitMeasuresForm}>
form={measuresForm} <View item={displayedValues?.data ?? {}} columns={editingColumns} />
onFinish={handleSubmitMeasuresForm}
>
<View
item={displayedValues?.data ?? {}}
columns={editingColumns}
/>
</Form> </Form>
</LoaderPortal> </LoaderPortal>
</div> </div>
</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 { Grid, GridItem } from '../../components/Grid'
import '../../styles/index.css' import '../../styles/index.css'
const renderSwitchableColumn = (column, itm) => { const colsCount = 3
if(column.render) {
return ( const headerCellStyle = {
<Form.Item border:'1px solid lightgrey'
key={column.dataIndex}
name={column.dataIndex}
style={{margin: 0}}
//rules={column.formItemRules}
>
{column.render(itm[column.dataIndex])}
</Form.Item>
)
} }
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) if (!item || !columns?.length)
return <Empty key='empty' image={Empty.PRESENTED_IMAGE_SIMPLE}/> return <Empty key='empty' image={Empty.PRESENTED_IMAGE_SIMPLE}/>
const colsCount = 3 return (
const viewItems = columns.map( (column, i) => { <Grid>
const row = Math.floor(i / colsCount) + 1 {columns.map((column, i) => (
const colb = i % colsCount <>
return <>
<GridItem <GridItem
key={column.dataIndex} key={column.dataIndex}
row={row} row={Math.floor(i / colsCount) + 1}
col={colb*2 + 1} col={(i % colsCount) * 2 + 1}
style={{border:'1px solid lightgrey'}} style={headerCellStyle}
> >
{column.title} {column.title}
</GridItem> </GridItem>
<GridItem <GridItem
key={column.title} key={column.title}
row={row} row={Math.floor(i / colsCount) + 1}
col={colb*2 + 2} col={(i % colsCount) * 2 + 2}
style={{border:'1px solid lightgrey', style={valueCellStyle}
justifyContent:'right',
marginRight:'16px',
fontWeight:'bold',
textAlign:'right',
padding: 0}}
> >
{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> </GridItem>
</> </>
}) ))}
return <>
<Grid>
{viewItems}
</Grid> </Grid>
</> )
} })

View File

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

View File

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