diff --git a/src/components/Table/Columns/index.ts b/src/components/Table/Columns/index.ts index ee6776a..de3a044 100755 --- a/src/components/Table/Columns/index.ts +++ b/src/components/Table/Columns/index.ts @@ -31,10 +31,6 @@ export type DataType = Record export type RenderMethod = (value: T, dataset?: DataType, index?: number) => ReactNode export type SorterMethod = (a?: DataType | null, b?: DataType | null) => number -/* -other - объект с дополнительными свойствами колонки -поддерживаются все базовые свойства из описания https://ant.design/components/table/#Column -плю дополнительные для колонок EditableTable: */ export type columnPropsOther = ColumnProps & { // редактируемая колонка editable?: boolean diff --git a/src/pages/Telemetry/Operations/DrillerList.jsx b/src/pages/Telemetry/Operations/DrillerList.jsx index c3f078d..c6f5980 100644 --- a/src/pages/Telemetry/Operations/DrillerList.jsx +++ b/src/pages/Telemetry/Operations/DrillerList.jsx @@ -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 }) => { diff --git a/src/pages/Telemetry/Operations/DrillerSchedule.jsx b/src/pages/Telemetry/Operations/DrillerSchedule.jsx index 5b9ba48..50e8508 100644 --- a/src/pages/Telemetry/Operations/DrillerSchedule.jsx +++ b/src/pages/Telemetry/Operations/DrillerSchedule.jsx @@ -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 ( <> { 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 }) => { diff --git a/src/pages/Telemetry/Operations/OperationsTable.jsx b/src/pages/Telemetry/Operations/OperationsTable.jsx index dfee059..2c67d18 100644 --- a/src/pages/Telemetry/Operations/OperationsTable.jsx +++ b/src/pages/Telemetry/Operations/OperationsTable.jsx @@ -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 }) => ( diff --git a/src/pages/Telemetry/Operations/TargetEditor.jsx b/src/pages/Telemetry/Operations/TargetEditor.jsx new file mode 100644 index 0000000..415175d --- /dev/null +++ b/src/pages/Telemetry/Operations/TargetEditor.jsx @@ -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 ( + <> + + + + + + ) +}) + +export default TargetEditor diff --git a/src/pages/Telemetry/Operations/index.jsx b/src/pages/Telemetry/Operations/index.jsx index 18411fd..0287ff2 100644 --- a/src/pages/Telemetry/Operations/index.jsx +++ b/src/pages/Telemetry/Operations/index.jsx @@ -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(() => { )} + {permissions.detectedOperation.get && permissions.operationValue.get && ( + + )}
- - + +
diff --git a/src/utils/functions/permissions.tsx b/src/utils/functions/permissions.tsx index deed6a7..5b9f869 100644 --- a/src/utils/functions/permissions.tsx +++ b/src/utils/functions/permissions.tsx @@ -20,7 +20,7 @@ export function isRequestType(value: string): value is ServiceRequestType { export const getPermissions = (...values: PermissionRequest[]) => { const permissions: Record>> = {} 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')