diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/WellboreUidMappingModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/WellboreUidMappingModal.tsx index 72c7258cc..f91f7eabc 100644 --- a/Src/WitsmlExplorer.Frontend/components/Modals/WellboreUidMappingModal.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Modals/WellboreUidMappingModal.tsx @@ -32,12 +32,13 @@ import WellWellboreTree, { export interface WellboreUidMappingModalProps { wellbore: Wellbore; targetServer: Server; + onModalClose?: () => void; } const WellboreUidMappingModal = ( props: WellboreUidMappingModalProps ): React.ReactElement => { - const { wellbore, targetServer } = props; + const { wellbore, targetServer, onModalClose } = props; const { dispatchOperation, @@ -201,6 +202,10 @@ const WellboreUidMappingModal = ( } else { await UidMappingService.addUidMapping(uidMapping); } + + if (onModalClose) { + onModalClose(); + } } }; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/WellboreUidMappingOverviewModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/WellboreUidMappingOverviewModal.tsx new file mode 100644 index 000000000..de96e0405 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/WellboreUidMappingOverviewModal.tsx @@ -0,0 +1,346 @@ +import { Server } from "../../models/server.ts"; +import React, { useEffect, useMemo, useState } from "react"; +import { useGetServers } from "../../hooks/query/useGetServers.tsx"; +import { useGetWellbores } from "../../hooks/query/useGetWellbores.tsx"; +import { UidMapping, UidMappingDbQuery } from "../../models/uidMapping.tsx"; +import UidMappingService from "../../services/uidMappingService.tsx"; +import { + ContentTable, + ContentTableColumn, + ContentTableRow, + ContentType +} from "../ContentViews/table"; +import { Colors } from "../../styles/Colors.tsx"; +import styled from "styled-components"; +import { Autocomplete, Dialog } from "@equinor/eds-core-react"; +import { useOperationState } from "../../hooks/useOperationState.tsx"; +import OperationType from "../../contexts/operationType.ts"; +import { ProgressSpinnerOverlay } from "../ProgressSpinner.tsx"; +import { Button } from "../StyledComponents/Button.tsx"; +import WellboreUidMappingModal, { + WellboreUidMappingModalProps +} from "./WellboreUidMappingModal.tsx"; +import { DisplayModalAction } from "../../contexts/operationStateReducer.tsx"; +import { calculateWellboreNodeId } from "../../models/wellbore.tsx"; + +interface UidMappingRow extends ContentTableRow { + sourceWellId: string; + sourceWellName: string; + sourceWellboreId: string; + sourceWellboreName: string; + targetWellId: string; + targetWellName: string; + targetWellboreId: string; + targetWellboreName: string; + username: string; + timestamp: string; +} + +const WellboreUidMappingOverviewModal = (): React.ReactElement => { + const { servers } = useGetServers(); + const { + dispatchOperation, + operationState: { colors } + } = useOperationState(); + + const [sourceServerValue, setSourceServerValue] = useState(undefined); + const [targetServerValue, setTargetServerValue] = useState(undefined); + + const [isFetchingUidMapping, setIsFetchingUidMapping] = + useState(false); + const [mappings, setMappings] = useState([]); + + const [selectedRows, setSelectedRows] = useState([]); + + const { wellbores: sourceWellbores, isFetching: isFetchingSourceWellbores } = + useGetWellbores(sourceServerValue, "", { placeholderData: [] }); + + const { wellbores: targetWellbores, isFetching: isFetchingTargetWellbores } = + useGetWellbores(targetServerValue, "", { placeholderData: [] }); + + useEffect(() => { + if ( + !!sourceServerValue && + !!targetServerValue && + !isFetchingSourceWellbores && + !isFetchingTargetWellbores + ) { + loadMappings(); + } + }, [sourceServerValue, targetServerValue, sourceWellbores, targetWellbores]); + + const loadMappings = () => { + const dbQuery: UidMappingDbQuery = { + sourceServerId: sourceServerValue.id, + targetServerId: targetServerValue.id + }; + + setSelectedRows([]); + + setIsFetchingUidMapping(true); + + UidMappingService.queryUidMapping(dbQuery) + .then((mappings) => { + if (mappings.length > 0) { + setMappings(mappings); + } else { + setMappings([]); + } + }) + .finally(() => setIsFetchingUidMapping(false)); + }; + + const tableData = useMemo((): UidMappingRow[] => { + const mappingRows: UidMappingRow[] = []; + + if (!!sourceServerValue && !!targetServerValue) { + for (const mapping of mappings) { + const sourceWellbore = sourceWellbores.find( + (wb) => wb.uid === mapping.sourceWellboreId + ); + + const targetWellbore = targetWellbores.find( + (wb) => wb.uid === mapping.targetWellboreId + ); + + const row: UidMappingRow = { + id: + calculateWellboreNodeId(sourceWellbore) + + calculateWellboreNodeId(targetWellbore), + sourceWellId: sourceWellbore.wellUid, + sourceWellName: sourceWellbore.wellName, + sourceWellboreId: sourceWellbore.uid, + sourceWellboreName: sourceWellbore.name, + targetWellId: targetWellbore.wellUid, + targetWellName: targetWellbore.wellName, + targetWellboreId: targetWellbore.uid, + targetWellboreName: targetWellbore.name, + username: mapping.username, + timestamp: mapping.timestamp + }; + + mappingRows.push(row); + } + } + + return mappingRows; + }, [ + isFetchingUidMapping, + mappings, + sourceWellbores, + targetWellbores, + sourceServerValue, + targetServerValue + ]); + + const columns: ContentTableColumn[] = [ + { + property: "sourceWellboreName", + label: "Source Wellbore Name", + type: ContentType.String + }, + { + property: "sourceWellboreId", + label: "Source Wellbore Id", + type: ContentType.String + }, + { + property: "sourceWellName", + label: "Source Well Name", + type: ContentType.String + }, + { + property: "sourceWellId", + label: "Source Well Id", + type: ContentType.String + }, + { + property: "targetWellboreName", + label: "Target Wellbore Name", + type: ContentType.String + }, + { + property: "targetWellboreId", + label: "Target Wellbore Id", + type: ContentType.String + }, + { + property: "targetWellName", + label: "Target Well Name", + type: ContentType.String + }, + { + property: "targetWellId", + label: "Target Well Id", + type: ContentType.String + }, + { property: "username", label: "User Name", type: ContentType.String }, + { property: "timestamp", label: "Timestamp", type: ContentType.String } + ]; + + const onDelete = async () => { + if (selectedRows.length > 0) { + for (const row of selectedRows) { + const deleteQuery: UidMappingDbQuery = { + sourceServerId: sourceServerValue.id, + sourceWellId: row.sourceWellId, + sourceWellboreId: row.sourceWellboreId, + targetServerId: targetServerValue.id, + targetWellId: row.targetWellId, + targetWellboreId: row.targetWellboreId + }; + + await UidMappingService.removeUidMapping(deleteQuery); + } + loadMappings(); + } + }; + + const onEdit = () => { + if (selectedRows.length == 1) { + const wellboreUidMappingModalProps: WellboreUidMappingModalProps = { + wellbore: sourceWellbores.find( + (wb) => wb.uid === selectedRows[0].sourceWellboreId + ), + targetServer: targetServerValue, + onModalClose: () => loadMappings() + }; + const action: DisplayModalAction = { + type: OperationType.DisplayModal, + payload: + }; + dispatchOperation(action); + } + }; + + const onClose = () => { + dispatchOperation({ type: OperationType.HideModal }); + }; + + const dialogStyle = { + width: "960px", + height: "75vh", + background: colors.ui.backgroundDefault, + color: colors.text.staticIconsDefault + }; + + return ( + <> + + + Wellbore UID Mapping Overview + + + + + + `${s.name}`} + options={servers} + onOptionsChange={(changes) => { + setMappings([]); + setSourceServerValue(changes.selectedItems[0] as Server); + }} + colors={colors} + /> + + + `${s.name}`} + options={servers} + onOptionsChange={(changes) => { + setMappings([]); + setTargetServerValue(changes.selectedItems[0] as Server); + }} + colors={colors} + /> + + + + {isFetchingUidMapping || + isFetchingSourceWellbores || + isFetchingTargetWellbores ? ( + + ) : ( + + setSelectedRows(rows as UidMappingRow[]) + } + /> + )} + + + + + + + + + + + + + ); +}; + +const StyledAutocomplete = styled(Autocomplete)<{ colors: Colors }>` + button { + color: ${(props) => props.colors.infographic.primaryMossGreen}; + } +`; + +const ContentLayout = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + height: 82%; + gap: 0.75rem; +`; + +const HeaderLayout = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-start; + gap: 0.75rem; +`; + +const ServerItemLayout = styled.div` + display: flex; + flex-direction: row; + gap: 0.75rem; +`; + +const BodyLayout = styled.div` + display: flex; + flex-direction: column; + gap: 0.75rem; + min-height: 49vh; +`; + +const FooterLayout = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 0.75rem; +`; + +const StyledProgressSpinner = styled(ProgressSpinnerOverlay)` + Overlay { + width: 96%; + height: 65%; + } +`; + +export default WellboreUidMappingOverviewModal; diff --git a/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx b/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx index 39c41fe3d..b59b9fd3d 100644 --- a/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx @@ -14,6 +14,7 @@ import { getJobsViewPath, getQueryViewPath } from "routes/utils/pathBuilder"; import AuthorizationService from "services/authorizationService"; import styled from "styled-components"; import Icon from "styles/Icons"; +import WellboreUidMappingOverviewModal from "./Modals/WellboreUidMappingOverviewModal.tsx"; export default function TopRightCornerMenu() { const { dispatchOperation } = useOperationState(); @@ -30,6 +31,13 @@ export default function TopRightCornerMenu() { }); }; + const openUidModal = () => { + dispatchOperation({ + type: OperationType.DisplayModal, + payload: + }); + }; + const openCredentialsModal = () => { const userCredentialsModalProps: UserCredentialsModalProps = { server: connectedServer, @@ -96,6 +104,13 @@ export default function TopRightCornerMenu() { {showLabels && "Query"} +