From dd07633ec0671c16c16eb7be23529ee92bd8210f Mon Sep 17 00:00:00 2001 From: JonasABach Date: Wed, 23 Oct 2024 12:49:21 +0200 Subject: [PATCH 1/2] working revision details modal --- .../api/Controllers/RevisionsController.cs | 12 ++ backend/api/Dtos/Project/UpdateRevisionDto.cs | 10 ++ .../api/Services/Entities/IRevisionService.cs | 2 + .../api/Services/Entities/RevisionService.cs | 27 ++- .../Controls/RevisionDetailsModal.tsx | 169 ++++++++++++++++-- frontend/src/Components/Header.tsx | 7 +- frontend/src/Services/ProjectService.ts | 5 + frontend/src/Utils/RevisionUtils.tsx | 11 ++ frontend/src/types.d.ts | 29 ++- 9 files changed, 243 insertions(+), 29 deletions(-) create mode 100644 backend/api/Dtos/Project/UpdateRevisionDto.cs diff --git a/backend/api/Controllers/RevisionsController.cs b/backend/api/Controllers/RevisionsController.cs index 2129e0869..6757efee0 100644 --- a/backend/api/Controllers/RevisionsController.cs +++ b/backend/api/Controllers/RevisionsController.cs @@ -1,5 +1,6 @@ using api.Authorization; using api.Dtos; +using api.Models; using api.Services; using Microsoft.AspNetCore.Mvc; @@ -44,4 +45,15 @@ public async Task CreateProject([FromRoute] Guid projectId { return await _revisionService.CreateRevision(projectId, createRevisionDto); } + + [HttpPut("{revisionId}")] + [RequiresApplicationRoles( + ApplicationRole.Admin, + ApplicationRole.User + )] + [ActionType(ActionType.Edit)] + public async Task UpdateRevision([FromRoute] Guid projectId, [FromRoute] Guid revisionId, [FromBody] UpdateRevisionDto updateRevisionDto) + { + return await _revisionService.UpdateRevision(projectId, revisionId, updateRevisionDto); + } } diff --git a/backend/api/Dtos/Project/UpdateRevisionDto.cs b/backend/api/Dtos/Project/UpdateRevisionDto.cs new file mode 100644 index 000000000..c0f19abae --- /dev/null +++ b/backend/api/Dtos/Project/UpdateRevisionDto.cs @@ -0,0 +1,10 @@ + +using api.Models; + +namespace api.Dtos; + +public class UpdateRevisionDto +{ + public string Name { get; set; } = null!; + +} diff --git a/backend/api/Services/Entities/IRevisionService.cs b/backend/api/Services/Entities/IRevisionService.cs index 103332bd4..5c17ab28c 100644 --- a/backend/api/Services/Entities/IRevisionService.cs +++ b/backend/api/Services/Entities/IRevisionService.cs @@ -7,4 +7,6 @@ public interface IRevisionService { Task GetRevision(Guid projectId); Task CreateRevision(Guid projectId, CreateRevisionDto createRevisionDto); + Task UpdateRevision(Guid projectId, Guid revisionId, UpdateRevisionDto updateRevisionDto); + } diff --git a/backend/api/Services/Entities/RevisionService.cs b/backend/api/Services/Entities/RevisionService.cs index 75fbe665e..ab1b2e449 100644 --- a/backend/api/Services/Entities/RevisionService.cs +++ b/backend/api/Services/Entities/RevisionService.cs @@ -25,6 +25,7 @@ public class RevisionService : IRevisionService private readonly DcdDbContext _context; private readonly IMapper _mapper; private readonly IProjectRepository _projectRepository; + private readonly IMapperService _mapperService; public RevisionService( @@ -34,7 +35,8 @@ public RevisionService( IProjectAccessService projectAccessService, IProjectService projectService, IMapper mapper, - IProjectRepository projectRepository + IProjectRepository projectRepository, + IMapperService mapperService ) { _context = context; @@ -44,6 +46,7 @@ IProjectRepository projectRepository _projectService = projectService; _mapper = mapper; _projectRepository = projectRepository; + _mapperService = mapperService; } // TODO: Rewrite when CaseWithAssetsDto is no longer needed @@ -249,6 +252,28 @@ private async Task UpdateProjectWithRevisionChanges(Guid projectId, Cre return existingProject; } + public async Task UpdateRevision(Guid projectId, Guid revisionId, UpdateRevisionDto updateRevisionDto) + { + var project = await _projectRepository.GetProjectWithCases(projectId) + ?? throw new NotFoundInDBException($"Project with id {projectId} not found."); + + var revision = project.Revisions?.FirstOrDefault(r => r.Id == revisionId) + ?? throw new NotFoundInDBException($"Revision with id {revisionId} not found."); + + revision.Name = updateRevisionDto.Name; + + _context.Projects.Update(project); + await _context.SaveChangesAsync(); + + Activity.Current?.AddBaggage(nameof(project), JsonConvert.SerializeObject(project, Formatting.None, + new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + })); + var projectDto = _mapperService.MapToDto(project, projectId); + return projectDto; + } + private void SetProjectAndRelatedEntitiesToEmptyGuids(Project project, Guid originalProjectId, CreateRevisionDto createRevisionDto) { project.Id = Guid.Empty; diff --git a/frontend/src/Components/Controls/RevisionDetailsModal.tsx b/frontend/src/Components/Controls/RevisionDetailsModal.tsx index 6eb511a3f..2d3ce4067 100644 --- a/frontend/src/Components/Controls/RevisionDetailsModal.tsx +++ b/frontend/src/Components/Controls/RevisionDetailsModal.tsx @@ -1,45 +1,180 @@ -import React from "react" +import React, { + ChangeEventHandler, useEffect, useRef, useState, +} from "react" import { Typography, Icon, Button, + Divider, + InputWrapper, + TextField, + Chip, } from "@equinor/eds-core-react" -import { exit_to_app } from "@equinor/eds-icons" +import { checkbox_outline, exit_to_app, info_circle } from "@equinor/eds-icons" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { useModuleCurrentContext } from "@equinor/fusion-framework-react-module-context" +import styled from "styled-components" +import { useNavigate, useParams } from "react-router-dom" +import DialogContent from "@mui/material/DialogContent" +import { Grid } from "@mui/material" +import DialogActions from "@mui/material/DialogActions" import Modal from "../Modal/Modal" -type CaseDropMenuProps = { +import { formatFullDate } from "@/Utils/common" +import { projectQueryFn, revisionQueryFn } from "@/Services/QueryFunctions" +import { exitRevisionView, updateRevisionName } from "@/Utils/RevisionUtils" +import { useProjectContext } from "@/Context/ProjectContext" +import useEditProject from "@/Hooks/useEditProject" +import { GetProjectService } from "@/Services/ProjectService" +import { PROJECT_CLASSIFICATION, INTERNAL_PROJECT_PHASE } from "@/Utils/constants" + +type RevisionDetailsModalProps = { isMenuOpen: boolean; setIsMenuOpen: React.Dispatch>; }; -const CaseDropMenu: React.FC = ({ isMenuOpen, setIsMenuOpen }) => { - const exitRevisionView = () => { - console.log("Exiting revision view") - } +const Wrapper = styled.div` + flex-direction: row; + display: flex; + margin-bottom: 10px; +` + +const InfoIcon = styled(Icon)` + margin-right: 5px; + margin-top: -2px; +` + +const ColumnWrapper = styled.div` + display: flex; + flex-direction: column; + margin-bottom: 15px; + gap: 8px; +` + +const RevisionDetailsModal: React.FC = ({ + isMenuOpen, + setIsMenuOpen, +}) => { + const { currentContext } = useModuleCurrentContext() + const externalId = currentContext?.externalId + const { projectId } = useProjectContext() + const [isNameChanged, setIsNameChanged] = useState(false) + const { addProjectEdit } = useEditProject() + const [revisionName, setRevisionName] = useState("") + + const { revisionId } = useParams() + + const { data: projectApiData } = useQuery({ + queryKey: ["projectApiData", externalId], + queryFn: () => projectQueryFn(externalId), + enabled: !!externalId, + }) + + const { data: revisionApiData } = useQuery({ + queryKey: ["revisionApiData", revisionId], + queryFn: () => revisionQueryFn(projectId, revisionId), + enabled: !!revisionId, + }) + + useEffect(() => { + setRevisionName(revisionApiData?.name || "") + }, [revisionApiData]) const closeMenu = () => { setIsMenuOpen(false) } + const handleRevisionNameChange = async () => { + if (revisionApiData && projectId && revisionId) { + const updatedRevision = await updateRevisionName(projectId, revisionId, revisionName) + if (updatedRevision) { + addProjectEdit(updatedRevision.id, updatedRevision) + closeMenu() + } + } + } + + const handleNameInputChange: ChangeEventHandler = async (e) => { + setRevisionName(e.currentTarget.value) + setIsNameChanged(true) + } + + const newClassification = PROJECT_CLASSIFICATION[revisionApiData?.classification as keyof typeof PROJECT_CLASSIFICATION]?.label + const newInternalProjectPhase = revisionApiData?.internalProjectPhase as keyof typeof INTERNAL_PROJECT_PHASE + + if (!projectApiData && !revisionApiData) { return null } + return ( - Revisions are copies of a project at a given point in time. Revisions are locked for editing. - + + + + + Revisions are copies of a project at a given point in time. + Revisions are locked for editing. + + + + + + + + + + {projectApiData?.createDate ? formatFullDate(projectApiData.createDate) : "N/A"} + + + + + {INTERNAL_PROJECT_PHASE[newInternalProjectPhase]?.label ?? "N/A"} + + + + + + {newClassification ?? "N/A"} + + + + + + + + Quality checks performed + + + + + MDQC + + + + Arena + + + + )} actions={( -
- + - -
+ )} /> ) } -export default CaseDropMenu +export default RevisionDetailsModal diff --git a/frontend/src/Components/Header.tsx b/frontend/src/Components/Header.tsx index b8dfb0b64..0fec646d7 100644 --- a/frontend/src/Components/Header.tsx +++ b/frontend/src/Components/Header.tsx @@ -14,6 +14,10 @@ const RouteCoordinator = (): JSX.Element => { const { setIsCreating, setIsLoading, setSnackBarMessage, isLoading } = useAppContext() const { currentContext } = useModuleCurrentContext() + function isAxiosError(error: unknown): error is AxiosError { + return (error as AxiosError).isAxiosError !== undefined + } + const navigate = useNavigate() const location = useLocation() @@ -135,6 +139,3 @@ const RouteCoordinator = (): JSX.Element => { export default RouteCoordinator -function isAxiosError(error: unknown): error is AxiosError { - return (error as AxiosError).isAxiosError !== undefined -} diff --git a/frontend/src/Services/ProjectService.ts b/frontend/src/Services/ProjectService.ts index 03c275dad..a5281782e 100644 --- a/frontend/src/Services/ProjectService.ts +++ b/frontend/src/Services/ProjectService.ts @@ -36,6 +36,11 @@ export class __ProjectService extends __BaseService { return res } + public async updateRevision(projectId: string, revisionId: string, body: Components.Schemas.UpdateProjectDto): Promise { + const res = await this.put(`${projectId}/revisions/${revisionId}`, { body }) + return res + } + public async updateProject(projectId: string, body: Components.Schemas.UpdateProjectDto): Promise { const res = await this.put(`${projectId}`, { body }) return res diff --git a/frontend/src/Utils/RevisionUtils.tsx b/frontend/src/Utils/RevisionUtils.tsx index 1a8be4534..595dd4154 100644 --- a/frontend/src/Utils/RevisionUtils.tsx +++ b/frontend/src/Utils/RevisionUtils.tsx @@ -20,6 +20,17 @@ export const createRevision = async ( } } +export const updateRevisionName = async ( + projectId: string, + revisionId: string, + name: string, +) => { + const projectService = await GetProjectService() + const updateRevisionDto = { name } + const updatedRevision = await projectService.updateRevision(projectId, revisionId, updateRevisionDto) + return updatedRevision +} + export const navigateToRevision = ( revisionId: string, setIsRevision: React.Dispatch>, diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index 79c6a04c0..3ea35fff7 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -582,8 +582,8 @@ declare namespace Components { } export interface CreateRevisionDto { name: string; - internalProjectPhase: InternalProjectPhase /* int32 */; - classification: ProjectClassification /* int32 */; + internalProjectPhase?: InternalProjectPhase /* int32 */; + classification?: ProjectClassification /* int32 */; } export interface CreateSeismicAcquisitionAndProcessingDto { startYear?: number; // int32 @@ -1716,6 +1716,9 @@ declare namespace Components { discountRate?: number; // double exchangeRateUSDToNOK?: number; // double } + export interface UpdateRevisionDto { + name?: string | null; + } export interface UpdateSeismicAcquisitionAndProcessingDto { startYear?: number; // int32 values?: number /* double */[] | null; @@ -1991,14 +1994,10 @@ declare namespace Paths { namespace Projects$ProjectIdAccess { namespace Get { namespace Parameters { - export type ExternalId = string; // uuid - export type ProjectId = string; + export type ProjectId = string; // uuid } export interface PathParameters { - projectId: Parameters.ProjectId; - } - export interface QueryParameters { - externalId?: Parameters.ExternalId /* uuid */; + projectId: Parameters.ProjectId /* uuid */; } namespace Responses { export type $200 = Components.Schemas.AccessRightsDto; @@ -3733,6 +3732,20 @@ declare namespace Paths { export type $200 = Components.Schemas.ProjectWithAssetsDto; } } + namespace Put { + namespace Parameters { + export type ProjectId = string; // uuid + export type RevisionId = string; // uuid + } + export interface PathParameters { + projectId: Parameters.ProjectId /* uuid */; + revisionId: Parameters.RevisionId /* uuid */; + } + export type RequestBody = Components.Schemas.UpdateRevisionDto; + namespace Responses { + export type $200 = Components.Schemas.ProjectDto; + } + } } namespace Projects$ProjectIdTechnicalInput { namespace Put { From a39c77f7ad4e02ecd97b1ed6692242a15b6cd4a9 Mon Sep 17 00:00:00 2001 From: JonasABach Date: Thu, 24 Oct 2024 09:31:39 +0200 Subject: [PATCH 2/2] Update RevisionDetailsModal.tsx --- frontend/src/Components/Controls/RevisionDetailsModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Components/Controls/RevisionDetailsModal.tsx b/frontend/src/Components/Controls/RevisionDetailsModal.tsx index 2d3ce4067..ac2ec7b99 100644 --- a/frontend/src/Components/Controls/RevisionDetailsModal.tsx +++ b/frontend/src/Components/Controls/RevisionDetailsModal.tsx @@ -104,7 +104,7 @@ const RevisionDetailsModal: React.FC = ({ return (