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

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,198 +1,161 @@
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(() => { useEffect(() => {
const defaultValuesToDisplay = values[values.length-1] const data = [group.defaultValue].concat(group.values ?? [])
setData(data)
setDisplayedValues(defaultValuesToDisplay) setDisplayedValues(data.at(-1))
}, [values]) }, [group.defaultValue, group.values])
useEffect(() => { useEffect(() => {
let switchableColumns = [] const switchableColumns = createEditingColumns(
group.columns,
isTableEditing isTableEditing ? () => <Input className={'w-100 measure-input'} /> : null
? 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 () => {
measuresForm.validateFields()
const crudButtons = const measureParams = {
<div className='w-300px mt-8px'> idWell: idWell,
<Button idCategory: group.idCategory,
key='add' timestamp: new Date().toISOString(),
className='w-33' data: formData
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 = if(editingActionName === 'add') {
<div className='w-300px mt-8px'> await MeasureService.insert(idWell, measureParams)
<div className='d-flex'> } else if (editingActionName === 'edit') {
<Button measureParams.id = displayedValues.id
key='confirm' measureParams.timestamp = displayedValues.timestamp
className='w-50' await MeasureService.update(idWell, measureParams)
onClick={() => { measuresForm.submit() }} }
>
<SaveOutlined />
</Button>
<Button
key='decline'
className='w-50'
onClick={()=> setIsTableEditing(false)}
>
<CloseCircleOutlined />
</Button>
</div>
</div>
let handleSubmitMeasuresForm = async (formData) => { setIsTableEditing(false)
measuresForm.validateFields() updateMeasuresFunc()
},
setShowLoader,
`Не удалось добавить/изменить запись для скаважины "${idWell}"`
)
const measureParams = { return (
idWell: idWell, <>
idCategory: idCategory, &nbsp;<h2>{group.title}</h2>&nbsp;
timestamp: new Date().toISOString(), <div className={'d-flex'}>
data: formData <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>
setShowLoader(true) <div className={'measure-dates mt-20px'}>
<Timeline className={'mt-12px ml-10px'}>
if(editingActionName === 'add') { {data.map((item, index) =>
await MeasureService.insert(idWell, measureParams) <Timeline.Item
} else if (editingActionName === 'edit') { key={index}
measureParams.id = displayedValues.id className={'measure-button'}
measureParams.timestamp = displayedValues.timestamp onClick={() => setDisplayedValues(item)}
await MeasureService.update(idWell, measureParams) dot={item?.id !== displayedValues?.id ? null :
} <CheckSquareOutlined className={'timeline-clock-icon'} />
}
setIsTableEditing(false) >
updateMeasuresFunc() <span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}>
setShowLoader(false) {formatDate(item.timestamp)}
} </span>
</Timeline.Item>
return <> )}
&nbsp; </Timeline>
<h2>{title}</h2> </div>
&nbsp;
<div className='d-flex'>
<div className='flex-direction-column'>
<div className='measure-buttons-container'>
{isTableEditing
? confirmButtons
: crudButtons
}
</div> </div>
<div className={'w-100'}>
<div className='measure-dates mt-20px'> <LoaderPortal show={showLoader}>
<Timeline className='mt-12px ml-10px'> <Form form={measuresForm} onFinish={handleSubmitMeasuresForm}>
{values.map((item, index) => <View item={displayedValues?.data ?? {}} columns={editingColumns} />
<Timeline.Item </Form>
key={index} </LoaderPortal>
className='measure-button'
onClick={() => setDisplayedValues(item)}
dot={item?.id === displayedValues?.id
? <CheckSquareOutlined className="timeline-clock-icon" />
: null}
>
<span className={item?.id === displayedValues?.id ? 'selected-timeline' : ''}>
{item.timestamp ? moment.utc(item.timestamp).local().format(format) : 'Нет данных'}
</span>
</Timeline.Item>
)}
</Timeline>
</div> </div>
</div> </div>
<div className='w-100'> </>
<LoaderPortal show={showLoader}> )
<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 {Grid, GridItem} from '../../components/Grid' import { Empty, Form } from 'antd'
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 (
<Form.Item
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 headerCellStyle = {
border:'1px solid lightgrey'
} }
export const View = ({columns, item}) => { const valueCellStyle = {
border:'1px solid lightgrey',
justifyContent:'right',
marginRight:'16px',
fontWeight:'bold',
textAlign:'right',
padding: 0
}
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) => {
const row = Math.floor(i / colsCount) + 1
const colb = i % colsCount
return <>
<GridItem
key={column.dataIndex}
row={row}
col={colb*2 + 1}
style={{border:'1px solid lightgrey'}}
>
{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}}
>
{renderSwitchableColumn(column, item)}
</GridItem>
</>
})
return <>
<Grid> <Grid>
{viewItems} {columns.map((column, i) => (
<>
<GridItem
key={column.dataIndex}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 1}
style={headerCellStyle}
>
{column.title}
</GridItem>
<GridItem
key={column.title}
row={Math.floor(i / colsCount) + 1}
col={(i % colsCount) * 2 + 2}
style={valueCellStyle}
>
{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>
</>
))}
</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(
const measures = await MeasureService.getHisory(idWell) async () => {
setIsMeasuresUpdating(false) const measures = await MeasureService.getHisory(idWell)
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}>
<MeasureTable {data.map((group, idx) => (
idWell={idWell} <MeasureTable
idCategory={1} key={idx}
title='Замер бурового раствора' idWell={idWell}
columns={columnsDrillingFluid} group={group}
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%
} }