* Добавлено таблица редактирования целей в операциях.

* Исправлена ошибка именования в getPermissions
* Обновлено содержимое таблицы на странице операций
This commit is contained in:
goodmice 2022-06-14 17:04:01 +05:00
parent f133728e1b
commit ddecbadfc6
7 changed files with 202 additions and 43 deletions

View File

@ -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

View File

@ -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>
</>

View File

@ -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>
</>

View File

@ -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 }) => (

View 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

View File

@ -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>

View File

@ -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')