forked from ddrilling/asb_cloud_front
* Добавлено таблица редактирования целей в операциях.
* Исправлена ошибка именования в getPermissions * Обновлено содержимое таблицы на странице операций
This commit is contained in:
parent
f133728e1b
commit
ddecbadfc6
@ -31,10 +31,6 @@ export type DataType<T = any> = Record<string, T>
|
||||
export type RenderMethod<T = any> = (value: T, dataset?: DataType<T>, index?: number) => ReactNode
|
||||
export type SorterMethod<T = any> = (a?: DataType<T> | null, b?: DataType<T> | null) => number
|
||||
|
||||
/*
|
||||
other - объект с дополнительными свойствами колонки
|
||||
поддерживаются все базовые свойства из описания https://ant.design/components/table/#Column
|
||||
плю дополнительные для колонок EditableTable: */
|
||||
export type columnPropsOther<T = any> = ColumnProps<T> & {
|
||||
// редактируемая колонка
|
||||
editable?: boolean
|
||||
|
@ -4,16 +4,20 @@ import { Button, Modal } from 'antd'
|
||||
import { EditableTable, makeTextColumn } from '@components/Table'
|
||||
import { DrillerService } from '@api'
|
||||
|
||||
const reqRule = [{ message: 'Обязательное поле!', required: true }]
|
||||
|
||||
const rowClassName = (record) => record.has ? 'driller_list_active' : ''
|
||||
const columnOptions = {
|
||||
editable: true,
|
||||
formItemRules: [{ message: 'Обязательное поле!', required: true }]
|
||||
}
|
||||
|
||||
const columns = [
|
||||
makeTextColumn('Фамилия', 'surname', undefined, undefined, undefined, { editable: true, formItemRules: reqRule }),
|
||||
makeTextColumn('Имя', 'name', undefined, undefined, undefined, { editable: true, formItemRules: reqRule }),
|
||||
makeTextColumn('Фамилия', 'surname', undefined, undefined, undefined, columnOptions),
|
||||
makeTextColumn('Имя', 'name', undefined, undefined, undefined, columnOptions),
|
||||
makeTextColumn('Отчество', 'patronymic', undefined, undefined, undefined, { editable: true }),
|
||||
]
|
||||
|
||||
const rowClassName = (record) => record.has ? 'driller_list_active' : ''
|
||||
const scroll = { y: '75vh', scrollToFirstRowOnChange: true }
|
||||
|
||||
export const DrillerList = memo(({ loading, drillers, onChange }) => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
@ -26,6 +30,8 @@ export const DrillerList = memo(({ loading, drillers, onChange }) => {
|
||||
setShowModal(false)
|
||||
}, [])
|
||||
|
||||
const isLoading = useMemo(() => loading || showLoader, [loading, showLoader])
|
||||
|
||||
const tableHandlers = useMemo(() => {
|
||||
const handlerProps = {
|
||||
service: DrillerService,
|
||||
@ -39,7 +45,7 @@ export const DrillerList = memo(({ loading, drillers, onChange }) => {
|
||||
edit: { ...handlerProps, action: 'update', actionName: 'Редактирование бурильщика' },
|
||||
delete: { ...handlerProps, action: 'delete', actionName: 'Удаление бурильщика', permission: 'Driller.delete' },
|
||||
}
|
||||
}, [updateTable])
|
||||
}, [onChange])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -55,10 +61,10 @@ export const DrillerList = memo(({ loading, drillers, onChange }) => {
|
||||
bordered
|
||||
size={'small'}
|
||||
pagination={false}
|
||||
loading={loading || showLoader}
|
||||
loading={isLoading}
|
||||
dataSource={drillers}
|
||||
columns={columns}
|
||||
scroll={{ y: '75vh', scrollToFirstRowOnChange: true }}
|
||||
scroll={scroll}
|
||||
onRowAdd={tableHandlers.add}
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
@ -67,7 +73,7 @@ export const DrillerList = memo(({ loading, drillers, onChange }) => {
|
||||
</Modal>
|
||||
<Button
|
||||
onClick={onModalOpen}
|
||||
loading={loading || showLoader}
|
||||
loading={isLoading}
|
||||
style={{ marginRight: '10px' }}
|
||||
>Список бурильщиков</Button>
|
||||
</>
|
||||
|
@ -10,9 +10,15 @@ import {
|
||||
makeSelectColumn,
|
||||
} from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
import { ScheduleService } from '@api'
|
||||
|
||||
const reqRule = [{ message: 'Обязательное поле!', required: true }]
|
||||
const columnOptions = {
|
||||
editable: true,
|
||||
formItemRules: [{ message: 'Обязательное поле!', required: true }]
|
||||
}
|
||||
|
||||
const scroll = { y: '75vh', scrollToFirstRowOnChange: true }
|
||||
|
||||
export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
const [modalVisible, setScheduleModalVisible] = useState(false)
|
||||
@ -23,9 +29,12 @@ export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
|
||||
const updateSchedule = useCallback(async () => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const schedule = await ScheduleService.getByIdWell(idWell)
|
||||
const schedule = arrayOrDefault(await ScheduleService.getByIdWell(idWell))
|
||||
setSchedule(schedule)
|
||||
}
|
||||
},
|
||||
setShowLoader,
|
||||
'Не удалось загрузить расписания',
|
||||
'Получение списка расписаний',
|
||||
), [idWell])
|
||||
|
||||
const onModalOpen = useCallback(() => {
|
||||
@ -36,12 +45,10 @@ export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
setScheduleModalVisible(false)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
updateSchedule()
|
||||
}, [updateSchedule])
|
||||
|
||||
const newScheduleParser = useCallback((record) => ({ ...record, idWell }), [idWell])
|
||||
|
||||
const isLoading = useMemo(() => loading || showLoader, [loading, showLoader])
|
||||
|
||||
const tableHandlers = useMemo(() => {
|
||||
const handlerProps = {
|
||||
service: ScheduleService,
|
||||
@ -55,7 +62,7 @@ export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
|
||||
return {
|
||||
add: { ...handlerProps, action: 'insert', actionName: 'Добавление расписания', newScheduleParser },
|
||||
edit: { ...handlerProps, action: 'update', actionName: 'Редактирование расписания' },
|
||||
edit: { ...handlerProps, action: 'update', actionName: 'Редактирование расписания', newScheduleParser },
|
||||
delete: { ...handlerProps, action: 'delete', actionName: 'Удаление расписания', permission: 'Schedule.delete' },
|
||||
}
|
||||
}, [updateSchedule, newScheduleParser])
|
||||
@ -67,25 +74,26 @@ export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
}))
|
||||
|
||||
return [
|
||||
makeSelectColumn('Бурильщик', 'idDriller', options, undefined, {
|
||||
editable: true,
|
||||
formItemRules: reqRule,
|
||||
}, {
|
||||
makeSelectColumn('Бурильщик', 'idDriller', options, undefined, columnOptions, {
|
||||
showSearch: true,
|
||||
filterOption: (input, option) =>
|
||||
String(option?.label ?? '').toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}),
|
||||
makeGroupColumn('Рабочий период', [
|
||||
makeDateColumn('Начало', 'drillStart', undefined, undefined, { editable: true, formItemRules: reqRule }),
|
||||
makeDateColumn('Конец', 'drillEnd', undefined, undefined, { editable: true, formItemRules: reqRule }),
|
||||
makeDateColumn('Начало', 'drillStart', undefined, undefined, columnOptions),
|
||||
makeDateColumn('Конец', 'drillEnd', undefined, undefined, columnOptions),
|
||||
]),
|
||||
makeGroupColumn('Смена', [
|
||||
makeTimeColumn('Начало', 'shiftStart', undefined, undefined, { editable: true, formItemRules: reqRule }),
|
||||
makeTimeColumn('Конец', 'shiftEnd', undefined, undefined, { editable: true, formItemRules: reqRule }),
|
||||
makeTimeColumn('Начало', 'shiftStart', undefined, undefined, columnOptions),
|
||||
makeTimeColumn('Конец', 'shiftEnd', undefined, undefined, columnOptions),
|
||||
]),
|
||||
]
|
||||
}, [drillers])
|
||||
|
||||
useEffect(() => {
|
||||
updateSchedule()
|
||||
}, [updateSchedule])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@ -100,11 +108,11 @@ export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
sticky
|
||||
bordered
|
||||
size={'small'}
|
||||
scroll={scroll}
|
||||
pagination={false}
|
||||
loading={loading || showLoader}
|
||||
loading={isLoading}
|
||||
dataSource={schedule}
|
||||
columns={scheduleColumns}
|
||||
scroll={{ y: '75vh', scrollToFirstRowOnChange: true }}
|
||||
onRowAdd={tableHandlers.add}
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
@ -112,7 +120,7 @@ export const DrillerSchedule = memo(({ drillers, loading, onChange }) => {
|
||||
</Modal>
|
||||
<Button
|
||||
onClick={onModalOpen}
|
||||
loading={loading || showLoader}
|
||||
loading={isLoading}
|
||||
style={{ marginRight: '10px' }}
|
||||
>Расписание бурильщиков</Button>
|
||||
</>
|
||||
|
@ -1,14 +1,34 @@
|
||||
import { memo } from 'react'
|
||||
|
||||
import { Table, makeTextColumn, makeNumericColumn, makeDateColumn, makeNumericRender } from '@components/Table'
|
||||
import { Table, makeTextColumn, makeNumericColumn, makeNumericRender } from '@components/Table'
|
||||
|
||||
import '@styles/detected_operations.less'
|
||||
|
||||
const numericRender = makeNumericRender(2)
|
||||
|
||||
const drillerRender = (driller) => `${driller?.surname ?? ''} ${driller?.name ?? ''} ${driller?.patronymic ?? ''}`
|
||||
|
||||
const makeDrillerSorter = (key) => (a, b) => {
|
||||
const drillerA = a?.[key]
|
||||
const drillerB = b?.[key]
|
||||
|
||||
if (!drillerA && !drillerB) return 0
|
||||
if (!drillerA) return -1
|
||||
if (!drillerB) return 1
|
||||
|
||||
const strA = drillerRender(drillerA)
|
||||
const strB = drillerRender(drillerB)
|
||||
|
||||
return strA.localeCompare(strB)
|
||||
}
|
||||
|
||||
export const columns = [
|
||||
makeTextColumn('Пользователь', 'telemetryUserName', null, null, null, { width: 200 }),
|
||||
makeDateColumn('Дата начала', 'dateStart', null, undefined, { width: 150 }),
|
||||
makeNumericColumn('Продолжительность (мин)', 'durationMinutes', null, null, makeNumericRender(2), 150),
|
||||
makeNumericColumn('Глубина (м)', 'depthStart', null, null, makeNumericRender(1), 100),
|
||||
makeTextColumn('Бурила', 'driller', null, makeDrillerSorter('driller'), drillerRender, { width: 200 }),
|
||||
makeNumericColumn('Кол-во операций', 'count', null, null, (value) => parseInt(value), 150),
|
||||
makeNumericColumn('Среднее по ключевому показателю', 'averageValue', null, null, numericRender, 150),
|
||||
makeNumericColumn('Среднее целевого показателя', 'averageTargetValue', null, null, numericRender, 150),
|
||||
makeNumericColumn('Эффективность (%)', 'efficiency', null, null, numericRender, 150),
|
||||
makeNumericColumn('Коэффициент потерь', 'loss', null, null, numericRender, 100),
|
||||
]
|
||||
|
||||
export const OperationsTable = memo(({ data, height, ...other }) => (
|
||||
|
125
src/pages/Telemetry/Operations/TargetEditor.jsx
Normal file
125
src/pages/Telemetry/Operations/TargetEditor.jsx
Normal file
@ -0,0 +1,125 @@
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Button, Modal } from 'antd'
|
||||
|
||||
import { useIdWell } from '@asb/context'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { EditableTable, makeGroupColumn, makeNumericColumn, makeNumericRender, makeSelectColumn } from '@components/Table'
|
||||
import { DetectedOperationService, OperationValueService } from '@api'
|
||||
import { arrayOrDefault } from '@utils'
|
||||
|
||||
const columnOptions = {
|
||||
editable: true,
|
||||
formItemRules: [{ message: 'Обязательное поле!', required: true }]
|
||||
}
|
||||
|
||||
const scroll = { y: '75vh', scrollToFirstRowOnChange: true }
|
||||
const numericRender = makeNumericRender(2)
|
||||
|
||||
export const TargetEditor = memo(({ loading }) => {
|
||||
const [targets, setTargets] = useState([])
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [targetColumns, setTargetColumns] = useState([])
|
||||
|
||||
const idWell = useIdWell()
|
||||
|
||||
const updateTable = useCallback(async () => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const targets = arrayOrDefault(await OperationValueService.getByIdWell(idWell))
|
||||
setTargets(targets)
|
||||
},
|
||||
setShowLoader,
|
||||
'Не удалось загрузить цели',
|
||||
'Получение списка целей',
|
||||
), [idWell])
|
||||
|
||||
const onModalOpen = useCallback(() => {
|
||||
setShowModal(true)
|
||||
}, [])
|
||||
|
||||
const onModalCancel = useCallback(() => {
|
||||
setShowModal(false)
|
||||
}, [])
|
||||
|
||||
const recordParser = useCallback((record) => ({ ...record, idWell }), [idWell])
|
||||
|
||||
const isLoading = useMemo(() => loading || showLoader, [loading, showLoader])
|
||||
|
||||
const tableHandlers = useMemo(() => {
|
||||
const handlerProps = {
|
||||
service: OperationValueService,
|
||||
setLoader: setShowLoader,
|
||||
onComplete: updateTable,
|
||||
permission: 'OperationValue.edit',
|
||||
}
|
||||
|
||||
return {
|
||||
add: { ...handlerProps, action: 'insert', actionName: 'Добавление цели', recordParser },
|
||||
edit: { ...handlerProps, action: 'update', actionName: 'Редактирование цели', recordParser },
|
||||
delete: { ...handlerProps, action: 'delete', actionName: 'Удаление цели', permission: 'OperationValue.delete' },
|
||||
}
|
||||
}, [updateTable, recordParser])
|
||||
|
||||
useEffect(() => {
|
||||
invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const categories = arrayOrDefault(await DetectedOperationService.getCategories(idWell))
|
||||
const options = categories.map(({ id, name }) => ({ value: id, label: name }))
|
||||
|
||||
setTargetColumns([
|
||||
makeSelectColumn('Название', 'idOperationCategory', options, undefined, { ...columnOptions, width: 200 }, {
|
||||
showSearch: true,
|
||||
filterOption: (input, option) =>
|
||||
String(option?.label ?? '').toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}),
|
||||
makeNumericColumn('Цель', 'targetValue', undefined, undefined, numericRender, 150, columnOptions),
|
||||
makeNumericColumn('Норм.', 'standardValue', undefined, undefined, numericRender, 150, columnOptions),
|
||||
makeGroupColumn('Глубина, м', [
|
||||
makeNumericColumn('Начало', 'depthStart', undefined, undefined, numericRender, 150, columnOptions),
|
||||
makeNumericColumn('Окончание', 'depthEnd', undefined, undefined, numericRender, 150, columnOptions),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setShowLoader,
|
||||
`Не удалось получить список категорий целей`,
|
||||
'Получение списка категорий целей'
|
||||
)
|
||||
}, [idWell])
|
||||
|
||||
useEffect(() => {
|
||||
updateTable()
|
||||
}, [updateTable])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
centered
|
||||
width={1000}
|
||||
footer={null}
|
||||
visible={showModal}
|
||||
onCancel={onModalCancel}
|
||||
title={'Цели бурения'}
|
||||
>
|
||||
<EditableTable
|
||||
bordered
|
||||
size={'small'}
|
||||
scroll={scroll}
|
||||
pagination={false}
|
||||
loading={isLoading}
|
||||
dataSource={targets}
|
||||
columns={targetColumns}
|
||||
onRowAdd={tableHandlers.add}
|
||||
onRowEdit={tableHandlers.edit}
|
||||
onRowDelete={tableHandlers.delete}
|
||||
/>
|
||||
</Modal>
|
||||
<Button
|
||||
onClick={onModalOpen}
|
||||
loading={isLoading}
|
||||
style={{ marginRight: '10px' }}
|
||||
>Цели</Button>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default TargetEditor
|
@ -6,10 +6,11 @@ import { useIdWell } from '@asb/context'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { DateRangeWrapper } from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||
import { getPermissions, arrayOrDefault, range, wrapPrivateComponent } from '@utils'
|
||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||
|
||||
import DrillerList from './DrillerList'
|
||||
import TargetEditor from './TargetEditor'
|
||||
import DrillerSchedule from './DrillerSchedule'
|
||||
import OperationsChart from './OperationsChart'
|
||||
import OperationsTable from './OperationsTable'
|
||||
@ -21,13 +22,13 @@ const Operations = memo(() => {
|
||||
const [dateRange, setDateRange] = useState([])
|
||||
const [yDomain, setYDomain] = useState(20)
|
||||
const [dates, setDates] = useState()
|
||||
const [data, setData] = useState([])
|
||||
const [data, setData] = useState({ operations: [], stats: [] })
|
||||
const [drillers, setDrillers] = useState([])
|
||||
const [drillersLoader, setDrillersLoader] = useState(false)
|
||||
|
||||
const idWell = useIdWell()
|
||||
|
||||
const permissions = useMemo(() => getPermissions('Driller.get'), [])
|
||||
const permissions = useMemo(() => getPermissions('Driller.get', 'DetectedOperation.get', 'OperationValue.get'), [])
|
||||
|
||||
const disabledDates = useCallback((current) => current && !moment(current).isBetween(...dateRange, 'day', '[]'), [dateRange])
|
||||
|
||||
@ -108,11 +109,14 @@ const Operations = memo(() => {
|
||||
<DrillerList drillers={drillers} loading={drillersLoader} onChange={updateDrillers} />
|
||||
</>
|
||||
)}
|
||||
{permissions.detectedOperation.get && permissions.operationValue.get && (
|
||||
<TargetEditor />
|
||||
)}
|
||||
</div>
|
||||
<LoaderPortal show={isLoading}>
|
||||
<div className={'page-main'}>
|
||||
<OperationsChart data={data} height={'50vh'} yDomain={yDomain} />
|
||||
<OperationsTable data={data} height={'20vh'} />
|
||||
<OperationsChart data={data.operations} height={'50vh'} yDomain={yDomain} />
|
||||
<OperationsTable data={data.stats} height={'20vh'} />
|
||||
</div>
|
||||
</LoaderPortal>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@ export function isRequestType(value: string): value is ServiceRequestType {
|
||||
export const getPermissions = (...values: PermissionRequest[]) => {
|
||||
const permissions: Record<string, Partial<Record<ServiceRequestType, boolean>>> = {}
|
||||
values.forEach((key) => {
|
||||
const [service, type] = key.toLowerCase().split('.')
|
||||
const [service, type] = (key[0].toLowerCase() + key.slice(1)).split('.') // toCamelCase
|
||||
if (!isRequestType(type)) return
|
||||
permissions[service] = permissions[service] ?? {}
|
||||
permissions[service][type] = hasPermission(key) || (isDev() && getUserLogin() === 'dev')
|
||||
|
Loading…
Reference in New Issue
Block a user